mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 22:13:26 +00:00
auth: Make supported authentication backends a bitfield on realm.
This makes it possible to configure only certain authentication methods to be enabled on a per-realm basis. Note that the authentication_methods_dict function (which checks what backends are supported on the realm) requires an in function import due to a circular dependency.
This commit is contained in:
@@ -4,8 +4,10 @@ from typing import Dict, Any
|
|||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import ujson
|
import ujson
|
||||||
|
from zerver.models import get_realm_by_string_id
|
||||||
from zproject.backends import (password_auth_enabled, dev_auth_enabled,
|
from zproject.backends import (password_auth_enabled, dev_auth_enabled,
|
||||||
google_auth_enabled, github_auth_enabled)
|
google_auth_enabled, github_auth_enabled)
|
||||||
|
from zerver.lib.utils import get_subdomain
|
||||||
|
|
||||||
def add_settings(request):
|
def add_settings(request):
|
||||||
# type: (HttpRequest) -> Dict[str, Any]
|
# type: (HttpRequest) -> Dict[str, Any]
|
||||||
@@ -13,7 +15,11 @@ def add_settings(request):
|
|||||||
realm = request.user.realm
|
realm = request.user.realm
|
||||||
realm_uri = realm.uri
|
realm_uri = realm.uri
|
||||||
else:
|
else:
|
||||||
realm = None
|
if settings.REALMS_HAVE_SUBDOMAINS:
|
||||||
|
subdomain = get_subdomain(request)
|
||||||
|
realm = get_realm_by_string_id(subdomain)
|
||||||
|
else:
|
||||||
|
realm = None
|
||||||
# TODO: Figure out how to add an assertion that this is not used
|
# TODO: Figure out how to add an assertion that this is not used
|
||||||
realm_uri = settings.SERVER_URI
|
realm_uri = settings.SERVER_URI
|
||||||
|
|
||||||
@@ -38,9 +44,9 @@ def add_settings(request):
|
|||||||
'email_gateway_example': settings.EMAIL_GATEWAY_EXAMPLE,
|
'email_gateway_example': settings.EMAIL_GATEWAY_EXAMPLE,
|
||||||
'open_realm_creation': settings.OPEN_REALM_CREATION,
|
'open_realm_creation': settings.OPEN_REALM_CREATION,
|
||||||
'password_auth_enabled': password_auth_enabled(realm),
|
'password_auth_enabled': password_auth_enabled(realm),
|
||||||
'dev_auth_enabled': dev_auth_enabled(),
|
'dev_auth_enabled': dev_auth_enabled(realm),
|
||||||
'google_auth_enabled': google_auth_enabled(),
|
'google_auth_enabled': google_auth_enabled(realm),
|
||||||
'github_auth_enabled': github_auth_enabled(),
|
'github_auth_enabled': github_auth_enabled(realm),
|
||||||
'development_environment': settings.DEVELOPMENT,
|
'development_environment': settings.DEVELOPMENT,
|
||||||
'support_email': settings.ZULIP_ADMINISTRATOR,
|
'support_email': settings.ZULIP_ADMINISTRATOR,
|
||||||
}
|
}
|
||||||
|
|||||||
20
zerver/migrations/0040_realm_authentication_methods.py
Normal file
20
zerver/migrations/0040_realm_authentication_methods.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import bitfield.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0039_realmalias_drop_uniqueness'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='realm',
|
||||||
|
name='authentication_methods',
|
||||||
|
field=bitfield.models.BitField(['Google', 'Email', 'GitHub', 'LDAP', 'Dev', 'RemoteUser'], default=2147483647),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -10,6 +10,7 @@ from django.db.models import Manager
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AbstractBaseUser, UserManager, \
|
from django.contrib.auth.models import AbstractBaseUser, UserManager, \
|
||||||
PermissionsMixin
|
PermissionsMixin
|
||||||
|
import django.contrib.auth
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from zerver.lib.cache import cache_with_key, flush_user_profile, flush_realm, \
|
from zerver.lib.cache import cache_with_key, flush_user_profile, flush_realm, \
|
||||||
user_profile_by_id_cache_key, user_profile_by_email_cache_key, \
|
user_profile_by_id_cache_key, user_profile_by_email_cache_key, \
|
||||||
@@ -163,9 +164,29 @@ class Realm(ModelReprMixin, models.Model):
|
|||||||
notifications_stream = models.ForeignKey('Stream', related_name='+', null=True, blank=True) # type: Optional[Stream]
|
notifications_stream = models.ForeignKey('Stream', related_name='+', null=True, blank=True) # type: Optional[Stream]
|
||||||
deactivated = models.BooleanField(default=False) # type: bool
|
deactivated = models.BooleanField(default=False) # type: bool
|
||||||
default_language = models.CharField(default=u'en', max_length=MAX_LANGUAGE_ID_LENGTH) # type: text_type
|
default_language = models.CharField(default=u'en', max_length=MAX_LANGUAGE_ID_LENGTH) # type: text_type
|
||||||
|
authentication_methods = BitField(flags=AUTHENTICATION_FLAGS,
|
||||||
|
default=2**31 - 1) # type: BitHandler
|
||||||
|
|
||||||
DEFAULT_NOTIFICATION_STREAM_NAME = u'announce'
|
DEFAULT_NOTIFICATION_STREAM_NAME = u'announce'
|
||||||
|
|
||||||
|
def authentication_methods_dict(self):
|
||||||
|
# type: () -> Dict[text_type, bool]
|
||||||
|
"""Returns the a mapping from authentication flags to their status,
|
||||||
|
showing only those authentication flags that are supported on
|
||||||
|
the current server (i.e. if EmailAuthBackend is not configured
|
||||||
|
on the server, this will not return an entry for "Email")."""
|
||||||
|
# This mapping needs to be imported from here due to the cyclic
|
||||||
|
# dependency.
|
||||||
|
from zproject.backends import AUTH_BACKEND_NAME_MAP
|
||||||
|
|
||||||
|
ret = {} # type: Dict[text_type, bool]
|
||||||
|
supported_backends = {backend.__class__ for backend in django.contrib.auth.get_backends()}
|
||||||
|
for k, v in self.authentication_methods.iteritems():
|
||||||
|
backend = AUTH_BACKEND_NAME_MAP[k]
|
||||||
|
if backend in supported_backends:
|
||||||
|
ret[k] = v
|
||||||
|
return ret
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
# type: () -> text_type
|
# type: () -> text_type
|
||||||
return u"<Realm: %s %s>" % (self.domain, self.id)
|
return u"<Realm: %s %s>" % (self.domain, self.id)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from confirmation.models import Confirmation
|
|||||||
from zproject.backends import ZulipDummyBackend, EmailAuthBackend, \
|
from zproject.backends import ZulipDummyBackend, EmailAuthBackend, \
|
||||||
GoogleMobileOauth2Backend, ZulipRemoteUserBackend, ZulipLDAPAuthBackend, \
|
GoogleMobileOauth2Backend, ZulipRemoteUserBackend, ZulipLDAPAuthBackend, \
|
||||||
ZulipLDAPUserPopulator, DevAuthBackend, GitHubAuthBackend, ZulipAuthMixin, \
|
ZulipLDAPUserPopulator, DevAuthBackend, GitHubAuthBackend, ZulipAuthMixin, \
|
||||||
password_auth_enabled, github_auth_enabled
|
password_auth_enabled, github_auth_enabled, AUTH_BACKEND_NAME_MAP
|
||||||
|
|
||||||
from zerver.views.auth import maybe_send_to_registration
|
from zerver.views.auth import maybe_send_to_registration
|
||||||
|
|
||||||
@@ -96,6 +96,18 @@ class AuthBackendTest(TestCase):
|
|||||||
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipDummyBackend',)):
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipDummyBackend',)):
|
||||||
self.assertIsNone(backend.authenticate(username, *good_args, **good_kwargs))
|
self.assertIsNone(backend.authenticate(username, *good_args, **good_kwargs))
|
||||||
|
|
||||||
|
# Verify auth fails if the auth backend is disabled for the realm
|
||||||
|
for backend_name in AUTH_BACKEND_NAME_MAP.keys():
|
||||||
|
if isinstance(backend, AUTH_BACKEND_NAME_MAP[backend_name]):
|
||||||
|
break
|
||||||
|
|
||||||
|
index = getattr(user_profile.realm.authentication_methods, backend_name).number
|
||||||
|
user_profile.realm.authentication_methods.set_bit(index, False)
|
||||||
|
user_profile.realm.save()
|
||||||
|
self.assertIsNone(backend.authenticate(username, *good_args, **good_kwargs))
|
||||||
|
user_profile.realm.authentication_methods.set_bit(index, True)
|
||||||
|
user_profile.realm.save()
|
||||||
|
|
||||||
def test_dummy_backend(self):
|
def test_dummy_backend(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
self.verify_backend(ZulipDummyBackend(),
|
self.verify_backend(ZulipDummyBackend(),
|
||||||
|
|||||||
@@ -36,8 +36,12 @@ def pad_method_dict(method_dict):
|
|||||||
|
|
||||||
def auth_enabled_helper(backends_to_check, realm):
|
def auth_enabled_helper(backends_to_check, realm):
|
||||||
# type: (List[text_type], Optional[Realm]) -> bool
|
# type: (List[text_type], Optional[Realm]) -> bool
|
||||||
enabled_method_dict = dict((method, True) for method in Realm.AUTHENTICATION_FLAGS)
|
if realm is not None:
|
||||||
pad_method_dict(enabled_method_dict)
|
enabled_method_dict = realm.authentication_methods_dict()
|
||||||
|
pad_method_dict(enabled_method_dict)
|
||||||
|
else:
|
||||||
|
enabled_method_dict = dict((method, True) for method in Realm.AUTHENTICATION_FLAGS)
|
||||||
|
pad_method_dict(enabled_method_dict)
|
||||||
for supported_backend in django.contrib.auth.get_backends():
|
for supported_backend in django.contrib.auth.get_backends():
|
||||||
for backend_name in backends_to_check:
|
for backend_name in backends_to_check:
|
||||||
backend = AUTH_BACKEND_NAME_MAP[backend_name]
|
backend = AUTH_BACKEND_NAME_MAP[backend_name]
|
||||||
|
|||||||
Reference in New Issue
Block a user