mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	Migrate the google SSO from openid to oauth2
(imported from commit 6938c1cc5d245cc5642043279470365ff04df903)
This commit is contained in:
		@@ -41,7 +41,7 @@ $(function () {
 | 
			
		||||
            company email address to sign up. Otherwise, we won’t be able to
 | 
			
		||||
            connect you with your coworkers.</div>
 | 
			
		||||
            <div class="register-google">
 | 
			
		||||
                <a href="/accounts/login/openid/" class="zocial google register-google-button">Sign up with Google</a>
 | 
			
		||||
                <a href="{% url 'zerver.views.start_google_oauth2' %}" class="zocial google register-google-button">Sign up with Google</a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -92,7 +92,7 @@ autofocus('#id_username');
 | 
			
		||||
 | 
			
		||||
    {% if not desktop_sso_dispatch %}
 | 
			
		||||
    <div class="login-google">
 | 
			
		||||
    or <a href="/accounts/login/openid/" class="login-google-button zocial google">Sign in with Google</a>
 | 
			
		||||
    or <a href="{% url 'zerver.views.start_google_oauth2' %}" class="login-google-button zocial google">Sign in with Google</a>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ from django.contrib.auth.views import login as django_login_page, \
 | 
			
		||||
    logout_then_login as django_logout_then_login
 | 
			
		||||
from django.db.models import Q, F
 | 
			
		||||
from django.core.mail import send_mail, EmailMessage
 | 
			
		||||
from django.middleware.csrf import get_token
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from zerver.models import Message, UserProfile, Stream, Subscription, \
 | 
			
		||||
    Recipient, Realm, UserMessage, bulk_get_recipients, \
 | 
			
		||||
@@ -74,6 +75,8 @@ from zproject.backends import password_auth_enabled
 | 
			
		||||
 | 
			
		||||
from confirmation.models import Confirmation
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
import subprocess
 | 
			
		||||
import calendar
 | 
			
		||||
import datetime
 | 
			
		||||
@@ -86,6 +89,8 @@ import time
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import jwt
 | 
			
		||||
import hashlib
 | 
			
		||||
import hmac
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
 | 
			
		||||
from zerver.lib.rest import rest_dispatch as _rest_dispatch
 | 
			
		||||
@@ -697,6 +702,83 @@ def handle_openid_errors(request, issue, openid_response=None):
 | 
			
		||||
def process_openid_login(request):
 | 
			
		||||
    return login_complete(request, render_failure=handle_openid_errors)
 | 
			
		||||
 | 
			
		||||
def google_oauth2_csrf(request, value):
 | 
			
		||||
    return hmac.new(get_token(request).encode('utf-8'), value, hashlib.sha256).hexdigest()
 | 
			
		||||
 | 
			
		||||
def start_google_oauth2(request):
 | 
			
		||||
    uri = 'https://accounts.google.com/o/oauth2/auth?'
 | 
			
		||||
    cur_time = str(int(time.time()))
 | 
			
		||||
    csrf_state = '{}:{}'.format(
 | 
			
		||||
        cur_time,
 | 
			
		||||
        google_oauth2_csrf(request, cur_time),
 | 
			
		||||
    )
 | 
			
		||||
    prams = {
 | 
			
		||||
        'response_type': 'code',
 | 
			
		||||
        'client_id': settings.GOOGLE_OAUTH2_CLIENT_ID,
 | 
			
		||||
        'redirect_uri': ''.join((
 | 
			
		||||
            settings.EXTERNAL_URI_SCHEME,
 | 
			
		||||
            settings.EXTERNAL_HOST,
 | 
			
		||||
            reverse('zerver.views.finish_google_oauth2'),
 | 
			
		||||
        )),
 | 
			
		||||
        'scope': 'profile email',
 | 
			
		||||
        'state': csrf_state,
 | 
			
		||||
    }
 | 
			
		||||
    return redirect(uri + urllib.urlencode(prams))
 | 
			
		||||
 | 
			
		||||
def finish_google_oauth2(request):
 | 
			
		||||
    error = request.GET.get('error')
 | 
			
		||||
    if error == 'access_denied':
 | 
			
		||||
        return redirect('/')
 | 
			
		||||
    elif error is not None:
 | 
			
		||||
        logging.error('Error from google oauth2 login %r', request.GET)
 | 
			
		||||
        return HttpResponse(status=400)
 | 
			
		||||
 | 
			
		||||
    value, hmac_value = request.GET.get('state').split(':')
 | 
			
		||||
    if hmac_value != google_oauth2_csrf(request, value):
 | 
			
		||||
        raise Exception('Google oauth2 CSRF error')
 | 
			
		||||
 | 
			
		||||
    resp = requests.post(
 | 
			
		||||
        'https://www.googleapis.com/oauth2/v3/token',
 | 
			
		||||
        data={
 | 
			
		||||
            'code': request.GET.get('code'),
 | 
			
		||||
            'client_id': settings.GOOGLE_OAUTH2_CLIENT_ID,
 | 
			
		||||
            'client_secret': settings.GOOGLE_OAUTH2_CLIENT_SECRET,
 | 
			
		||||
            'redirect_uri': ''.join((
 | 
			
		||||
                settings.EXTERNAL_URI_SCHEME,
 | 
			
		||||
                settings.EXTERNAL_HOST,
 | 
			
		||||
                reverse('zerver.views.finish_google_oauth2'),
 | 
			
		||||
            )),
 | 
			
		||||
            'grant_type': 'authorization_code',
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
    if resp.status_code != 200:
 | 
			
		||||
        raise Exception('Could not convert google pauth2 code to access_token\r%r' % resp.text)
 | 
			
		||||
    access_token = resp.json['access_token']
 | 
			
		||||
 | 
			
		||||
    resp = requests.get(
 | 
			
		||||
        'https://www.googleapis.com/plus/v1/people/me',
 | 
			
		||||
        params={'access_token': access_token}
 | 
			
		||||
    )
 | 
			
		||||
    if resp.status_code != 200:
 | 
			
		||||
        raise Exception('Google login failed making API call\r%r' % resp.text)
 | 
			
		||||
    body = resp.json
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        full_name = body['name']['formatted']
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        # Only google+ users have a formated name. I am ignoring i18n here.
 | 
			
		||||
        full_name = '{} {}'.format(
 | 
			
		||||
            body['name']['givenName'], body['name']['familyName']
 | 
			
		||||
        )
 | 
			
		||||
    for email in body['emails']:
 | 
			
		||||
        if email['type'] == 'account':
 | 
			
		||||
            break
 | 
			
		||||
    else:
 | 
			
		||||
        raise Exception('Google oauth2 account email not found %r' % body)
 | 
			
		||||
    email_address = email['value']
 | 
			
		||||
    user_profile = authenticate(username=email_address, use_dummy_backend=True)
 | 
			
		||||
    return login_or_register_remote_user(request, email_address, user_profile, full_name)
 | 
			
		||||
 | 
			
		||||
def login_page(request, **kwargs):
 | 
			
		||||
    template_response = django_login_page(
 | 
			
		||||
        request, authentication_form=OurAuthenticationForm, **kwargs)
 | 
			
		||||
 
 | 
			
		||||
@@ -143,6 +143,14 @@ else:
 | 
			
		||||
 | 
			
		||||
GOOGLE_CLIENT_ID = "835904834568-77mtr5mtmpgspj9b051del9i9r5t4g4n.apps.googleusercontent.com"
 | 
			
		||||
 | 
			
		||||
if DEPLOYED:
 | 
			
		||||
    GOOGLE_OAUTH2_CLIENT_ID = ''
 | 
			
		||||
    GOOGLE_OAUTH2_CLIENT_SECRET  = ''
 | 
			
		||||
else:
 | 
			
		||||
    # Google OAUTH2 for dev with the redirect uri set to http://localhost:9991/accounts/login/google/done/
 | 
			
		||||
    GOOGLE_OAUTH2_CLIENT_ID = '607830223128-4qgthc7ofdqce232dk690t5jgkm1ce33.apps.googleusercontent.com'
 | 
			
		||||
    GOOGLE_OAUTH2_CLIENT_SECRET  = 'xxxxxxxxxxxxxxxxxxxxxxxx'
 | 
			
		||||
 | 
			
		||||
# Administrator domain for this install
 | 
			
		||||
ADMIN_DOMAIN = "zulip.com"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,8 @@ urlpatterns = patterns('',
 | 
			
		||||
    url(r'^accounts/login/openid/done/$', 'django_openid_auth.views.login_complete', name='openid-complete'),
 | 
			
		||||
    url(r'^accounts/login/sso/$', 'zerver.views.remote_user_sso', name='login-sso'),
 | 
			
		||||
    url(r'^accounts/login/jwt/$', 'zerver.views.remote_user_jwt', name='login-jwt'),
 | 
			
		||||
    url(r'^accounts/login/google/$', 'zerver.views.start_google_oauth2'),
 | 
			
		||||
    url(r'^accounts/login/google/done/$', 'zerver.views.finish_google_oauth2'),
 | 
			
		||||
    # We have two entries for accounts/login to allow reverses on the Django
 | 
			
		||||
    # view we're wrapping to continue to function.
 | 
			
		||||
    url(r'^accounts/login/',  'zerver.views.login_page',         {'template_name': 'zerver/login.html'}),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user