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:
umkay 2016-11-02 13:41:10 -07:00 committed by Tim Abbott
parent b41c15fa05
commit 21c024fc29
5 changed files with 70 additions and 7 deletions

View File

@ -4,8 +4,10 @@ from typing import Dict, Any
from django.http import HttpRequest
from django.conf import settings
import ujson
from zerver.models import get_realm_by_string_id
from zproject.backends import (password_auth_enabled, dev_auth_enabled,
google_auth_enabled, github_auth_enabled)
from zerver.lib.utils import get_subdomain
def add_settings(request):
# type: (HttpRequest) -> Dict[str, Any]
@ -13,7 +15,11 @@ def add_settings(request):
realm = request.user.realm
realm_uri = realm.uri
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
realm_uri = settings.SERVER_URI
@ -38,9 +44,9 @@ def add_settings(request):
'email_gateway_example': settings.EMAIL_GATEWAY_EXAMPLE,
'open_realm_creation': settings.OPEN_REALM_CREATION,
'password_auth_enabled': password_auth_enabled(realm),
'dev_auth_enabled': dev_auth_enabled(),
'google_auth_enabled': google_auth_enabled(),
'github_auth_enabled': github_auth_enabled(),
'dev_auth_enabled': dev_auth_enabled(realm),
'google_auth_enabled': google_auth_enabled(realm),
'github_auth_enabled': github_auth_enabled(realm),
'development_environment': settings.DEVELOPMENT,
'support_email': settings.ZULIP_ADMINISTRATOR,
}

View 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),
),
]

View File

@ -10,6 +10,7 @@ from django.db.models import Manager
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, UserManager, \
PermissionsMixin
import django.contrib.auth
from django.dispatch import receiver
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, \
@ -163,9 +164,29 @@ class Realm(ModelReprMixin, models.Model):
notifications_stream = models.ForeignKey('Stream', related_name='+', null=True, blank=True) # type: Optional[Stream]
deactivated = models.BooleanField(default=False) # type: bool
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'
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):
# type: () -> text_type
return u"<Realm: %s %s>" % (self.domain, self.id)

View File

@ -29,7 +29,7 @@ from confirmation.models import Confirmation
from zproject.backends import ZulipDummyBackend, EmailAuthBackend, \
GoogleMobileOauth2Backend, ZulipRemoteUserBackend, ZulipLDAPAuthBackend, \
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
@ -96,6 +96,18 @@ class AuthBackendTest(TestCase):
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipDummyBackend',)):
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):
# type: () -> None
self.verify_backend(ZulipDummyBackend(),

View File

@ -36,8 +36,12 @@ def pad_method_dict(method_dict):
def auth_enabled_helper(backends_to_check, realm):
# type: (List[text_type], Optional[Realm]) -> bool
enabled_method_dict = dict((method, True) for method in Realm.AUTHENTICATION_FLAGS)
pad_method_dict(enabled_method_dict)
if realm is not None:
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 backend_name in backends_to_check:
backend = AUTH_BACKEND_NAME_MAP[backend_name]