diff --git a/zerver/context_processors.py b/zerver/context_processors.py index 854b195d0e..242d2e6adf 100644 --- a/zerver/context_processors.py +++ b/zerver/context_processors.py @@ -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, } diff --git a/zerver/migrations/0040_realm_authentication_methods.py b/zerver/migrations/0040_realm_authentication_methods.py new file mode 100644 index 0000000000..312454c907 --- /dev/null +++ b/zerver/migrations/0040_realm_authentication_methods.py @@ -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), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 0359fe2a91..3c6e239da0 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -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"" % (self.domain, self.id) diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index 43d0d9f14e..82c7902668 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -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(), diff --git a/zproject/backends.py b/zproject/backends.py index 71aac26260..a0a96d2a95 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -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]