diff --git a/zerver/context_processors.py b/zerver/context_processors.py index 491e4eb6d9..20913a5305 100644 --- a/zerver/context_processors.py +++ b/zerver/context_processors.py @@ -2,7 +2,8 @@ from __future__ import absolute_import from django.conf import settings import ujson -from zproject.backends import password_auth_enabled, dev_auth_enabled, google_auth_enabled +from zproject.backends import (password_auth_enabled, dev_auth_enabled, + google_auth_enabled, github_auth_enabled) def add_settings(request): realm = request.user.realm if hasattr(request.user, "realm") else None @@ -28,6 +29,7 @@ def add_settings(request): '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(), 'development_environment': settings.DEVELOPMENT, } diff --git a/zproject/backends.py b/zproject/backends.py index 74f7772f10..4bd68aaf82 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -13,6 +13,8 @@ from zerver.models import UserProfile, Realm, get_user_profile_by_id, \ from apiclient.sample_tools import client as googleapiclient from oauth2client.crypt import AppIdentityError +from social.backends.github import GithubOAuth2 +from django.contrib.auth import authenticate def password_auth_enabled(realm): if realm is not None: @@ -55,6 +57,12 @@ def common_get_active_user_by_email(email, return_data=None): return None return user_profile +def github_auth_enabled(): + for backend in django.contrib.auth.get_backends(): + if isinstance(backend, GitHubBackend): + return True + return False + class ZulipAuthMixin(object): def get_user(self, user_profile_id): """ Get a UserProfile object from the user_profile_id. """ @@ -208,3 +216,51 @@ class DevAuthBackend(ZulipAuthMixin): def authenticate(self, username, return_data=None): return common_get_active_user_by_email(username, return_data=return_data) + +class GitHubBackend(ZulipAuthMixin, GithubOAuth2): + def authenticate(self, *args, **kwargs): + try: + email_address = kwargs['response']['email'] + return_data = kwargs['return_data'] + except KeyError: + return None + + try: + user_profile = get_user_profile_by_email(email_address) + except UserProfile.DoesNotExist: + return_data["valid_attestation"] = True + return None + + if not user_profile.is_active: + return_data["inactive_user"] = True + return None + + if user_profile.realm.deactivated: + return_data["inactive_realm"] = True + return None + + return user_profile + + def do_auth(self, *args, **kwargs): + # This function needs to be imported from here due to the cyclic + # dependency. + from zerver.views import login_or_register_remote_user + + kwargs['return_data'] = {} + + user_profile = super(GitHubBackend, self).do_auth(*args, **kwargs) + + return_data = kwargs['return_data'] + inactive_user = return_data.get('inactive_user') + inactive_realm = return_data.get('inactive_realm') + + if inactive_user or inactive_realm: + return None + + request = self.strategy.request + details = kwargs['response'] + email_address = details.get('email') + full_name = details.get('name') + + return login_or_register_remote_user(request, email_address, + user_profile, full_name) diff --git a/zproject/settings.py b/zproject/settings.py index 51946ab369..084347f4d1 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -964,6 +964,14 @@ if POPULATE_PROFILE_VIA_LDAP and \ else: POPULATE_PROFILE_VIA_LDAP = 'zproject.backends.ZulipLDAPAuthBackend' in AUTHENTICATION_BACKENDS or POPULATE_PROFILE_VIA_LDAP +######################################################################## +# GITHUB AUTHENTICATION SETTINGS +######################################################################## +SOCIAL_AUTH_GITHUB_KEY = get_secret('social_auth_github_key') +SOCIAL_AUTH_GITHUB_SECRET = get_secret('social_auth_github_secret') +SOCIAL_AUTH_LOGIN_ERROR_URL = '/login/' +SOCIAL_AUTH_GITHUB_SCOPE = ['email'] + ######################################################################## # EMAIL SETTINGS ######################################################################## diff --git a/zproject/urls.py b/zproject/urls.py index ba3e00616e..e3e5a592f8 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -310,6 +310,9 @@ urls += [ url(r'^notify_tornado$', 'zerver.tornadoviews.notify'), ] +# Python Social Auth +urls += [url(r'^', include('social.apps.django_app.urls', namespace='social'))] + if settings.DEVELOPMENT: urls += dev_urls.urls i18n_urls += dev_urls.i18n_urls