mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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',
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user