mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
auth: Refactor social login rendering.
login_context now gets the social_backends list through get_social_backend_dicts and we move display_logo customization to backend class definition. This prepares for easily adding multiple IdP support in SAML authentication - there will be a social_backend dict for each configured IdP, also allowing display_name and icon customization per IdP.
This commit is contained in:
committed by
Tim Abbott
parent
9532e99800
commit
28dd1b34f2
BIN
static/images/landing-page/logos/azuread-icon.png
Normal file
BIN
static/images/landing-page/logos/azuread-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
static/images/landing-page/logos/github-icon.png
Normal file
BIN
static/images/landing-page/logos/github-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -632,36 +632,6 @@ button.login-social-button:active {
|
|||||||
box-shadow: 0px 1px 1px hsla(0, 0%, 0%, 0.3);
|
box-shadow: 0px 1px 1px hsla(0, 0%, 0%, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.saml-wrapper button.login-social-button {
|
|
||||||
background-image: url('/static/images/landing-page/logos/saml-icon.png');
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.google-wrapper button.login-social-button {
|
|
||||||
background-image: url('/static/images/landing-page/logos/googl_e-icon.png');
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github-wrapper::before {
|
|
||||||
content: "\f09b";
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
font-family: "FontAwesome";
|
|
||||||
font-size: 2rem;
|
|
||||||
color: hsl(0, 0%, 20%);
|
|
||||||
transform: translateX(15px) translateY(13px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.azuread-oauth2-wrapper::before {
|
|
||||||
content: "\f17a";
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
font-family: "FontAwesome";
|
|
||||||
font-size: 2rem;
|
|
||||||
color: hsl(0, 0%, 20%);
|
|
||||||
transform: translateX(15px) translateY(13px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-page-container .right-side .actions,
|
.login-page-container .right-side .actions,
|
||||||
.forgot-password-container .actions {
|
.forgot-password-container .actions {
|
||||||
margin: 20px 0px 0px;
|
margin: 20px 0px 0px;
|
||||||
|
|||||||
@@ -75,9 +75,9 @@ page can be easily identified in it's respective JavaScript file -->
|
|||||||
|
|
||||||
{% for backend in social_backends %}
|
{% for backend in social_backends %}
|
||||||
<div class="login-social">
|
<div class="login-social">
|
||||||
<form class="form-inline {{ backend.name }}-wrapper" action="{{ backend.signup_url }}" method="get">
|
<form class="form-inline" action="{{ backend.signup_url }}" method="get">
|
||||||
<input type='hidden' name='multiuse_object_key' value='{{ multiuse_object_key }}' />
|
<input type='hidden' name='multiuse_object_key' value='{{ multiuse_object_key }}' />
|
||||||
<button class="login-social-button full-width">
|
<button class="login-social-button full-width" style="background-image:url({{ backend.display_logo }})">
|
||||||
{{ _('Sign up with %(identity_provider)s', identity_provider=backend.display_name) }}
|
{{ _('Sign up with %(identity_provider)s', identity_provider=backend.display_name) }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -132,9 +132,9 @@ page can be easily identified in it's respective JavaScript file. -->
|
|||||||
|
|
||||||
{% for backend in social_backends %}
|
{% for backend in social_backends %}
|
||||||
<div class="login-social">
|
<div class="login-social">
|
||||||
<form class="social_login_form form-inline {{ backend.name }}-wrapper" action="{{ backend.login_url }}" method="get">
|
<form class="social_login_form form-inline" action="{{ backend.login_url }}" method="get">
|
||||||
<input type="hidden" name="next" value="{{ next }}">
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
<button class="login-social-button">
|
<button class="login-social-button" style="background-image:url({{ backend.display_logo }})">
|
||||||
{{ _('Log in with %(identity_provider)s', identity_provider=backend.display_name) }}
|
{{ _('Log in with %(identity_provider)s', identity_provider=backend.display_name) }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -676,6 +676,10 @@ html_rules = whitespace_rules + prose_style_rules + [
|
|||||||
'templates/zerver/email.html',
|
'templates/zerver/email.html',
|
||||||
'templates/zerver/email_log.html',
|
'templates/zerver/email_log.html',
|
||||||
|
|
||||||
|
# Social backend logos are dynamically loaded
|
||||||
|
'templates/zerver/accounts_home.html',
|
||||||
|
'templates/zerver/login.html',
|
||||||
|
|
||||||
# Probably just needs to be changed to display: none so the exclude works
|
# Probably just needs to be changed to display: none so the exclude works
|
||||||
'templates/zerver/app/navbar.html',
|
'templates/zerver/app/navbar.html',
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,15 @@ from urllib.parse import urljoin
|
|||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from zerver.models import UserProfile, get_realm, Realm
|
from zerver.models import UserProfile, get_realm, Realm
|
||||||
from zproject.backends import (
|
from zproject.backends import (
|
||||||
any_social_backend_enabled,
|
any_social_backend_enabled,
|
||||||
|
get_social_backend_dicts,
|
||||||
password_auth_enabled,
|
password_auth_enabled,
|
||||||
require_email_format_usernames,
|
require_email_format_usernames,
|
||||||
auth_enabled_helper,
|
auth_enabled_helper,
|
||||||
AUTH_BACKEND_NAME_MAP,
|
AUTH_BACKEND_NAME_MAP,
|
||||||
SOCIAL_AUTH_BACKENDS,
|
|
||||||
)
|
)
|
||||||
from zerver.decorator import get_client_name
|
from zerver.decorator import get_client_name
|
||||||
from zerver.lib.send_email import FromAddress
|
from zerver.lib.send_email import FromAddress
|
||||||
@@ -168,7 +167,6 @@ def login_context(request: HttpRequest) -> Dict[str, Any]:
|
|||||||
|
|
||||||
# Add the keys for our standard authentication backends.
|
# Add the keys for our standard authentication backends.
|
||||||
no_auth_enabled = True
|
no_auth_enabled = True
|
||||||
social_backends = []
|
|
||||||
for auth_backend_name in AUTH_BACKEND_NAME_MAP:
|
for auth_backend_name in AUTH_BACKEND_NAME_MAP:
|
||||||
name_lower = auth_backend_name.lower()
|
name_lower = auth_backend_name.lower()
|
||||||
key = "%s_auth_enabled" % (name_lower,)
|
key = "%s_auth_enabled" % (name_lower,)
|
||||||
@@ -177,19 +175,7 @@ def login_context(request: HttpRequest) -> Dict[str, Any]:
|
|||||||
if is_enabled:
|
if is_enabled:
|
||||||
no_auth_enabled = False
|
no_auth_enabled = False
|
||||||
|
|
||||||
# Now add the enabled social backends to the social_backends
|
context['social_backends'] = get_social_backend_dicts(realm)
|
||||||
# list used to generate buttons for login/register pages.
|
|
||||||
backend = AUTH_BACKEND_NAME_MAP[auth_backend_name]
|
|
||||||
if not is_enabled or backend not in SOCIAL_AUTH_BACKENDS:
|
|
||||||
continue
|
|
||||||
social_backends.append({
|
|
||||||
'name': backend.name,
|
|
||||||
'display_name': backend.auth_backend_name,
|
|
||||||
'login_url': reverse('login-social', args=(backend.name,)),
|
|
||||||
'signup_url': reverse('signup-social', args=(backend.name,)),
|
|
||||||
'sort_order': backend.sort_order,
|
|
||||||
})
|
|
||||||
context['social_backends'] = sorted(social_backends, key=lambda x: x['sort_order'], reverse=True)
|
|
||||||
context['no_auth_enabled'] = no_auth_enabled
|
context['no_auth_enabled'] = no_auth_enabled
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import logging
|
|||||||
import magic
|
import magic
|
||||||
import ujson
|
import ujson
|
||||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
from django_auth_ldap.backend import LDAPBackend, LDAPReverseEmailSearch, \
|
from django_auth_ldap.backend import LDAPBackend, LDAPReverseEmailSearch, \
|
||||||
_LDAPUser, ldap_error
|
_LDAPUser, ldap_error
|
||||||
@@ -992,6 +993,9 @@ def social_auth_finish(backend: Any,
|
|||||||
|
|
||||||
class SocialAuthMixin(ZulipAuthMixin):
|
class SocialAuthMixin(ZulipAuthMixin):
|
||||||
auth_backend_name = "undeclared"
|
auth_backend_name = "undeclared"
|
||||||
|
name = "undeclared"
|
||||||
|
display_logo = None # type: Optional[str]
|
||||||
|
|
||||||
# Used to determine how to order buttons on login form, backend with
|
# Used to determine how to order buttons on login form, backend with
|
||||||
# higher sort order are displayed first.
|
# higher sort order are displayed first.
|
||||||
sort_order = 0
|
sort_order = 0
|
||||||
@@ -1020,8 +1024,10 @@ class SocialAuthMixin(ZulipAuthMixin):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2):
|
class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2):
|
||||||
|
name = "github"
|
||||||
auth_backend_name = "GitHub"
|
auth_backend_name = "GitHub"
|
||||||
sort_order = 100
|
sort_order = 100
|
||||||
|
display_logo = "/static/images/landing-page/logos/github-icon.png"
|
||||||
|
|
||||||
def get_verified_emails(self, *args: Any, **kwargs: Any) -> List[str]:
|
def get_verified_emails(self, *args: Any, **kwargs: Any) -> List[str]:
|
||||||
access_token = kwargs["response"]["access_token"]
|
access_token = kwargs["response"]["access_token"]
|
||||||
@@ -1085,12 +1091,15 @@ class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2):
|
|||||||
|
|
||||||
class AzureADAuthBackend(SocialAuthMixin, AzureADOAuth2):
|
class AzureADAuthBackend(SocialAuthMixin, AzureADOAuth2):
|
||||||
sort_order = 50
|
sort_order = 50
|
||||||
|
name = "azuread-oauth2"
|
||||||
auth_backend_name = "AzureAD"
|
auth_backend_name = "AzureAD"
|
||||||
|
display_logo = "/static/images/landing-page/logos/azuread-icon.png"
|
||||||
|
|
||||||
class GoogleAuthBackend(SocialAuthMixin, GoogleOAuth2):
|
class GoogleAuthBackend(SocialAuthMixin, GoogleOAuth2):
|
||||||
sort_order = 150
|
sort_order = 150
|
||||||
auth_backend_name = "Google"
|
auth_backend_name = "Google"
|
||||||
name = "google"
|
name = "google"
|
||||||
|
display_logo = "/static/images/landing-page/logos/googl_e-icon.png"
|
||||||
|
|
||||||
def get_verified_emails(self, *args: Any, **kwargs: Any) -> List[str]:
|
def get_verified_emails(self, *args: Any, **kwargs: Any) -> List[str]:
|
||||||
verified_emails = [] # type: List[str]
|
verified_emails = [] # type: List[str]
|
||||||
@@ -1105,10 +1114,12 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
|
|||||||
standard_relay_params = ["subdomain", "multiuse_object_key", "mobile_flow_otp",
|
standard_relay_params = ["subdomain", "multiuse_object_key", "mobile_flow_otp",
|
||||||
"next", "is_signup"]
|
"next", "is_signup"]
|
||||||
REDIS_EXPIRATION_SECONDS = 60 * 15
|
REDIS_EXPIRATION_SECONDS = 60 * 15
|
||||||
|
name = "saml"
|
||||||
# Organization which go through the trouble of setting up SAML are most likely
|
# Organization which go through the trouble of setting up SAML are most likely
|
||||||
# to have it as their main authentication method, so it seems appropriate to have
|
# to have it as their main authentication method, so it seems appropriate to have
|
||||||
# SAML buttons at the top.
|
# SAML buttons at the top.
|
||||||
sort_order = 9999
|
sort_order = 9999
|
||||||
|
display_logo = "/static/images/landing-page/logos/saml-icon.png"
|
||||||
|
|
||||||
def auth_url(self) -> str:
|
def auth_url(self) -> str:
|
||||||
"""Get the URL to which we must redirect in order to
|
"""Get the URL to which we must redirect in order to
|
||||||
@@ -1222,6 +1233,34 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
SocialBackendDictT = TypedDict('SocialBackendDictT', {
|
||||||
|
'name': str,
|
||||||
|
'display_name': str,
|
||||||
|
'display_logo': str,
|
||||||
|
'login_url': str,
|
||||||
|
'signup_url': str,
|
||||||
|
'sort_order': int,
|
||||||
|
})
|
||||||
|
|
||||||
|
def create_standard_social_backend_dict(social_backend: SocialAuthMixin) -> SocialBackendDictT:
|
||||||
|
assert social_backend.display_logo is not None
|
||||||
|
return dict(
|
||||||
|
name=social_backend.name,
|
||||||
|
display_name=social_backend.auth_backend_name,
|
||||||
|
display_logo=social_backend.display_logo,
|
||||||
|
login_url=reverse('login-social', args=(social_backend.name,)),
|
||||||
|
signup_url=reverse('signup-social', args=(social_backend.name,)),
|
||||||
|
sort_order=social_backend.sort_order
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_social_backend_dicts(realm: Optional[Realm]=None) -> List[SocialBackendDictT]:
|
||||||
|
result = []
|
||||||
|
for backend in SOCIAL_AUTH_BACKENDS:
|
||||||
|
if auth_enabled_helper([backend.auth_backend_name], realm):
|
||||||
|
result.append(create_standard_social_backend_dict(backend))
|
||||||
|
|
||||||
|
return sorted(result, key=lambda x: x['sort_order'], reverse=True)
|
||||||
|
|
||||||
AUTH_BACKEND_NAME_MAP = {
|
AUTH_BACKEND_NAME_MAP = {
|
||||||
'Dev': DevAuthBackend,
|
'Dev': DevAuthBackend,
|
||||||
'Email': EmailAuthBackend,
|
'Email': EmailAuthBackend,
|
||||||
|
|||||||
Reference in New Issue
Block a user