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:
Mateusz Mandera
2019-10-22 18:11:28 +02:00
committed by Tim Abbott
parent 9532e99800
commit 28dd1b34f2
8 changed files with 49 additions and 50 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -632,36 +632,6 @@ button.login-social-button:active {
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,
.forgot-password-container .actions {
margin: 20px 0px 0px;

View File

@@ -75,9 +75,9 @@ page can be easily identified in it's respective JavaScript file -->
{% for backend in social_backends %}
<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 }}' />
<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) }}
</button>
</form>

View File

@@ -132,9 +132,9 @@ page can be easily identified in it's respective JavaScript file. -->
{% for backend in social_backends %}
<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 }}">
<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) }}
</button>
</form>

View File

@@ -676,6 +676,10 @@ html_rules = whitespace_rules + prose_style_rules + [
'templates/zerver/email.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
'templates/zerver/app/navbar.html',

View File

@@ -3,16 +3,15 @@ from urllib.parse import urljoin
from typing import Any, Dict, Optional
from django.http import HttpRequest
from django.conf import settings
from django.urls import reverse
from zerver.models import UserProfile, get_realm, Realm
from zproject.backends import (
any_social_backend_enabled,
get_social_backend_dicts,
password_auth_enabled,
require_email_format_usernames,
auth_enabled_helper,
AUTH_BACKEND_NAME_MAP,
SOCIAL_AUTH_BACKENDS,
)
from zerver.decorator import get_client_name
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.
no_auth_enabled = True
social_backends = []
for auth_backend_name in AUTH_BACKEND_NAME_MAP:
name_lower = auth_backend_name.lower()
key = "%s_auth_enabled" % (name_lower,)
@@ -177,19 +175,7 @@ def login_context(request: HttpRequest) -> Dict[str, Any]:
if is_enabled:
no_auth_enabled = False
# Now add the enabled social backends to the social_backends
# 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['social_backends'] = get_social_backend_dicts(realm)
context['no_auth_enabled'] = no_auth_enabled
return context

View File

@@ -17,6 +17,7 @@ import logging
import magic
import ujson
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from typing_extensions import TypedDict
from django_auth_ldap.backend import LDAPBackend, LDAPReverseEmailSearch, \
_LDAPUser, ldap_error
@@ -992,6 +993,9 @@ def social_auth_finish(backend: Any,
class SocialAuthMixin(ZulipAuthMixin):
auth_backend_name = "undeclared"
name = "undeclared"
display_logo = None # type: Optional[str]
# Used to determine how to order buttons on login form, backend with
# higher sort order are displayed first.
sort_order = 0
@@ -1020,8 +1024,10 @@ class SocialAuthMixin(ZulipAuthMixin):
return None
class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2):
name = "github"
auth_backend_name = "GitHub"
sort_order = 100
display_logo = "/static/images/landing-page/logos/github-icon.png"
def get_verified_emails(self, *args: Any, **kwargs: Any) -> List[str]:
access_token = kwargs["response"]["access_token"]
@@ -1085,12 +1091,15 @@ class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2):
class AzureADAuthBackend(SocialAuthMixin, AzureADOAuth2):
sort_order = 50
name = "azuread-oauth2"
auth_backend_name = "AzureAD"
display_logo = "/static/images/landing-page/logos/azuread-icon.png"
class GoogleAuthBackend(SocialAuthMixin, GoogleOAuth2):
sort_order = 150
auth_backend_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]:
verified_emails = [] # type: List[str]
@@ -1105,10 +1114,12 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
standard_relay_params = ["subdomain", "multiuse_object_key", "mobile_flow_otp",
"next", "is_signup"]
REDIS_EXPIRATION_SECONDS = 60 * 15
name = "saml"
# 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
# SAML buttons at the top.
sort_order = 9999
display_logo = "/static/images/landing-page/logos/saml-icon.png"
def auth_url(self) -> str:
"""Get the URL to which we must redirect in order to
@@ -1222,6 +1233,34 @@ class SAMLAuthBackend(SocialAuthMixin, SAMLAuth):
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 = {
'Dev': DevAuthBackend,
'Email': EmailAuthBackend,