mirror of
https://github.com/zulip/zulip.git
synced 2026-06-21 21:32:29 +08:00
auth: Merge RemoteUserBackend into external_authentication_methods.
We register ZulipRemoteUserBackend as an external_authentication_method
to make it show up in the corresponding field in the /server_settings
endpoint.
This also allows rendering its login button together with
Google/Github/etc. leading to us being able to get rid of some of the
code that was handling it as a special case - the js code for plumbing
the "next" value and the special {% if only_sso %} block in login.html.
An additional consequence of the login.html change is that now the
backend will have it button rendered even if it isn't the only backend
enabled on the server.
This commit is contained in:
parent
a842968090
commit
6dbd2b5fc3
@ -99,9 +99,6 @@ $(function () {
|
||||
const email_formaction = $("#login_form").attr('action');
|
||||
$("#login_form").attr('action', email_formaction + '/' + window.location.hash);
|
||||
$(".social_login_form input[name='next']").attr('value', '/' + window.location.hash);
|
||||
|
||||
const sso_address = $("#sso-login").attr('href');
|
||||
$("#sso-login").attr('href', sso_address + window.location.hash);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1045,7 +1045,6 @@ input.new-organization-button {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.login-sso,
|
||||
.login-social {
|
||||
max-width: 100%;
|
||||
min-width: 300px;
|
||||
|
||||
@ -14,140 +14,127 @@ page can be easily identified in it's respective JavaScript file. -->
|
||||
</div>
|
||||
|
||||
<div class="app-main login-page-container white-box inline-block">
|
||||
{% if only_sso %}
|
||||
{# SSO users don't have a password. #}
|
||||
|
||||
<div class="login-sso">
|
||||
<a id="sso-login" href="/accounts/login/sso/?next={{ next }}" class="btn btn-large btn-primary">
|
||||
{{ _('Log in with %(identity_provider)s', identity_provider="SSO") }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
{# Non-SSO users. #}
|
||||
|
||||
{% if realm_name %}
|
||||
<div class="left-side">
|
||||
<div class="org-header">
|
||||
<img class="avatar" src="{{ realm_icon }}" alt="" />
|
||||
<div class="info-box">
|
||||
<div class="organization-name">{{ realm_name }}</div>
|
||||
<div class="organization-path">{{ realm_uri }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ realm_description|safe }}
|
||||
{% if realm_name %}
|
||||
<div class="left-side">
|
||||
<div class="org-header">
|
||||
<img class="avatar" src="{{ realm_icon }}" alt="" />
|
||||
<div class="info-box">
|
||||
<div class="organization-name">{{ realm_name }}</div>
|
||||
<div class="organization-path">{{ realm_uri }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="right-side">
|
||||
{% if no_auth_enabled %}
|
||||
<div class="alert">
|
||||
<p>No authentication backends are enabled on this
|
||||
server yet, so it is impossible to login!</p>
|
||||
|
||||
<p>See the <a href="https://zulip.readthedocs.io/en/latest/production/install.html#step-3-configure-zulip">Zulip
|
||||
authentication documentation</a> to learn how to configure authentication backends.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if password_auth_enabled %}
|
||||
<form name="login_form" id="login_form" method="post" class="login-form"
|
||||
action="{{ url('django.contrib.auth.views.login') }}?next={{ next }}">
|
||||
|
||||
{% if two_factor_authentication_enabled %}
|
||||
{{ wizard.management_form }}
|
||||
{% endif %}
|
||||
{{ csrf_input }}
|
||||
|
||||
<!-- .no-validation is for removing the red star in CSS -->
|
||||
{% if not two_factor_authentication_enabled or wizard.steps.current == 'auth' %}
|
||||
<div class="input-box no-validation">
|
||||
<input id="id_username" type="{% if not require_email_format_usernames %}text{% else %}email{% endif %}"
|
||||
name="username" class="{% if require_email_format_usernames %}email {% endif %}required"
|
||||
{% if email %} value="{{ email }}" {% else %} value="" autofocus {% endif %}
|
||||
maxlength="72" required />
|
||||
<label for="id_username">
|
||||
{% if not require_email_format_usernames and email_auth_enabled %}
|
||||
{{ _('Email or username') }}
|
||||
{% elif not require_email_format_usernames %}
|
||||
{{ _('Username') }}
|
||||
{% else %}
|
||||
{{ _('Email') }}
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-box no-validation">
|
||||
<input id="id_password" name="password" class="required" type="password"
|
||||
{% if email %} autofocus {% endif %}
|
||||
required />
|
||||
<label for="id_password" class="control-label">{{ _('Password') }}</label>
|
||||
</div>
|
||||
{% else %}
|
||||
{% include "two_factor/_wizard_forms.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-error">
|
||||
{% for error in form.errors.values() %}
|
||||
<div>{{ error | striptags }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if already_registered %}
|
||||
<div class="alert">
|
||||
{{ _("You've already registered with this email address. Please log in below.") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if is_deactivated %}
|
||||
<div class="alert">
|
||||
{{ deactivated_account_error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if subdomain %}
|
||||
<div class="alert">
|
||||
{{ wrong_subdomain_error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<button type="submit" name="button" class="full-width">
|
||||
<img class="loader" src="/static/images/loader.svg" alt="" />
|
||||
<span class="text">{{ _("Log in") }}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if any_social_backend_enabled %}
|
||||
<div class="or"><span>{{ _('OR') }}</span></div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %} <!-- if password_auth_enabled -->
|
||||
|
||||
{% for backend in external_authentication_methods %}
|
||||
<div class="login-social">
|
||||
<form class="social_login_form form-inline" action="{{ backend.login_url }}" method="get">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="login-social-button" {% if backend.display_icon %} style="background-image:url({{ backend.display_icon }})" {% endif %}>
|
||||
{{ _('Log in with %(identity_provider)s', identity_provider=backend.display_name) }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="actions">
|
||||
{% if email_auth_enabled %}
|
||||
<a class="forgot-password" href="/accounts/password/reset/">{{ _('Forgot your password?') }}</a>
|
||||
{% endif %}
|
||||
{% if not register_link_disabled %}
|
||||
<a class="register-link float-right" href="/register/">{{ _('Sign up') }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="description">
|
||||
{{ realm_description|safe }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="right-side">
|
||||
{% if no_auth_enabled %}
|
||||
<div class="alert">
|
||||
<p>No authentication backends are enabled on this
|
||||
server yet, so it is impossible to login!</p>
|
||||
|
||||
<p>See the <a href="https://zulip.readthedocs.io/en/latest/production/install.html#step-3-configure-zulip">Zulip
|
||||
authentication documentation</a> to learn how to configure authentication backends.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if password_auth_enabled %}
|
||||
<form name="login_form" id="login_form" method="post" class="login-form"
|
||||
action="{{ url('django.contrib.auth.views.login') }}?next={{ next }}">
|
||||
|
||||
{% if two_factor_authentication_enabled %}
|
||||
{{ wizard.management_form }}
|
||||
{% endif %}
|
||||
{{ csrf_input }}
|
||||
|
||||
<!-- .no-validation is for removing the red star in CSS -->
|
||||
{% if not two_factor_authentication_enabled or wizard.steps.current == 'auth' %}
|
||||
<div class="input-box no-validation">
|
||||
<input id="id_username" type="{% if not require_email_format_usernames %}text{% else %}email{% endif %}"
|
||||
name="username" class="{% if require_email_format_usernames %}email {% endif %}required"
|
||||
{% if email %} value="{{ email }}" {% else %} value="" autofocus {% endif %}
|
||||
maxlength="72" required />
|
||||
<label for="id_username">
|
||||
{% if not require_email_format_usernames and email_auth_enabled %}
|
||||
{{ _('Email or username') }}
|
||||
{% elif not require_email_format_usernames %}
|
||||
{{ _('Username') }}
|
||||
{% else %}
|
||||
{{ _('Email') }}
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-box no-validation">
|
||||
<input id="id_password" name="password" class="required" type="password"
|
||||
{% if email %} autofocus {% endif %}
|
||||
required />
|
||||
<label for="id_password" class="control-label">{{ _('Password') }}</label>
|
||||
</div>
|
||||
{% else %}
|
||||
{% include "two_factor/_wizard_forms.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-error">
|
||||
{% for error in form.errors.values() %}
|
||||
<div>{{ error | striptags }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if already_registered %}
|
||||
<div class="alert">
|
||||
{{ _("You've already registered with this email address. Please log in below.") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if is_deactivated %}
|
||||
<div class="alert">
|
||||
{{ deactivated_account_error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if subdomain %}
|
||||
<div class="alert">
|
||||
{{ wrong_subdomain_error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<button type="submit" name="button" class="full-width">
|
||||
<img class="loader" src="/static/images/loader.svg" alt="" />
|
||||
<span class="text">{{ _("Log in") }}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if any_social_backend_enabled %}
|
||||
<div class="or"><span>{{ _('OR') }}</span></div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %} <!-- if password_auth_enabled -->
|
||||
|
||||
{% for backend in external_authentication_methods %}
|
||||
<div class="login-social">
|
||||
<form class="social_login_form form-inline" action="{{ backend.login_url }}" method="get">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="login-social-button" {% if backend.display_icon %} style="background-image:url({{ backend.display_icon }})" {% endif %}>
|
||||
{{ _('Log in with %(identity_provider)s', identity_provider=backend.display_name) }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="actions">
|
||||
{% if email_auth_enabled %}
|
||||
<a class="forgot-password" href="/accounts/password/reset/">{{ _('Forgot your password?') }}</a>
|
||||
{% endif %}
|
||||
{% if not register_link_disabled %}
|
||||
<a class="register-link float-right" href="/register/">{{ _('Sign up') }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1924,6 +1924,7 @@ class ExternalMethodDictsTests(ZulipTestCase):
|
||||
AUTHENTICATION_BACKENDS=('zproject.backends.EmailAuthBackend',
|
||||
'zproject.backends.GitHubAuthBackend',
|
||||
'zproject.backends.GoogleAuthBackend',
|
||||
'zproject.backends.ZulipRemoteUserBackend',
|
||||
'zproject.backends.SAMLAuthBackend',
|
||||
'zproject.backends.AzureADAuthBackend')
|
||||
):
|
||||
@ -1933,7 +1934,7 @@ class ExternalMethodDictsTests(ZulipTestCase):
|
||||
self.assertEqual(
|
||||
[social_backend['name'] for social_backend in external_auth_methods[1:]],
|
||||
[social_backend.name for social_backend in sorted(
|
||||
[GitHubAuthBackend, AzureADAuthBackend, GoogleAuthBackend],
|
||||
[ZulipRemoteUserBackend, GitHubAuthBackend, AzureADAuthBackend, GoogleAuthBackend],
|
||||
key=lambda x: x.sort_order,
|
||||
reverse=True
|
||||
)]
|
||||
|
||||
@ -244,25 +244,6 @@ class EmailAuthBackend(ZulipAuthMixin):
|
||||
return user_profile
|
||||
return None
|
||||
|
||||
class ZulipRemoteUserBackend(RemoteUserBackend):
|
||||
"""Authentication backend that reads the Apache REMOTE_USER variable.
|
||||
Used primarily in enterprise environments with an SSO solution
|
||||
that has an Apache REMOTE_USER integration. For manual testing, see
|
||||
|
||||
https://zulip.readthedocs.io/en/latest/production/authentication-methods.html
|
||||
|
||||
See also remote_user_sso in zerver/views/auth.py.
|
||||
"""
|
||||
create_unknown_user = False
|
||||
|
||||
def authenticate(self, *, remote_user: str, realm: Realm,
|
||||
return_data: Optional[Dict[str, Any]]=None) -> Optional[UserProfile]:
|
||||
if not auth_enabled_helper(["RemoteUser"], realm):
|
||||
return None
|
||||
|
||||
email = remote_user_to_email(remote_user)
|
||||
return common_get_active_user(email, realm, return_data=return_data)
|
||||
|
||||
def is_valid_email(email: str) -> bool:
|
||||
try:
|
||||
validate_email(email)
|
||||
@ -845,6 +826,42 @@ def external_auth_method(cls: Type[ExternalAuthMethod]) -> Type[ExternalAuthMeth
|
||||
EXTERNAL_AUTH_METHODS.append(cls)
|
||||
return cls
|
||||
|
||||
@external_auth_method
|
||||
class ZulipRemoteUserBackend(RemoteUserBackend, ExternalAuthMethod):
|
||||
"""Authentication backend that reads the Apache REMOTE_USER variable.
|
||||
Used primarily in enterprise environments with an SSO solution
|
||||
that has an Apache REMOTE_USER integration. For manual testing, see
|
||||
|
||||
https://zulip.readthedocs.io/en/latest/production/authentication-methods.html
|
||||
|
||||
See also remote_user_sso in zerver/views/auth.py.
|
||||
"""
|
||||
auth_backend_name = "RemoteUser"
|
||||
name = "remoteuser"
|
||||
display_icon = None
|
||||
sort_order = 9000 # If configured, this backend should have its button near the top of the list.
|
||||
|
||||
create_unknown_user = False
|
||||
|
||||
def authenticate(self, *, remote_user: str, realm: Realm,
|
||||
return_data: Optional[Dict[str, Any]]=None) -> Optional[UserProfile]:
|
||||
if not auth_enabled_helper(["RemoteUser"], realm):
|
||||
return None
|
||||
|
||||
email = remote_user_to_email(remote_user)
|
||||
return common_get_active_user(email, realm, return_data=return_data)
|
||||
|
||||
@classmethod
|
||||
def dict_representation(cls) -> List[ExternalAuthMethodDictT]:
|
||||
return [dict(
|
||||
name=cls.name,
|
||||
display_name="SSO",
|
||||
display_icon=cls.display_icon,
|
||||
# The user goes to the same URL for both login and signup:
|
||||
login_url=reverse('login-sso'),
|
||||
signup_url=reverse('login-sso'),
|
||||
)]
|
||||
|
||||
def redirect_deactivated_user_to_login() -> HttpResponseRedirect:
|
||||
# Specifying the template name makes sure that the user is not redirected to dev_login in case of
|
||||
# a deactivated account on a test server.
|
||||
@ -1417,7 +1434,6 @@ AUTH_BACKEND_NAME_MAP = {
|
||||
'Dev': DevAuthBackend,
|
||||
'Email': EmailAuthBackend,
|
||||
'LDAP': ZulipLDAPAuthBackend,
|
||||
'RemoteUser': ZulipRemoteUserBackend,
|
||||
} # type: Dict[str, Any]
|
||||
|
||||
for external_method in EXTERNAL_AUTH_METHODS:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user