diff --git a/templates/zerver/accounts_home.html b/templates/zerver/accounts_home.html
index c84ce9535a..a8ac7155f2 100644
--- a/templates/zerver/accounts_home.html
+++ b/templates/zerver/accounts_home.html
@@ -41,7 +41,7 @@ $(function () {
company email address to sign up. Otherwise, we won’t be able to
connect you with your coworkers.
diff --git a/templates/zerver/login.html b/templates/zerver/login.html
index 0af13f6155..0f37a8b3c1 100644
--- a/templates/zerver/login.html
+++ b/templates/zerver/login.html
@@ -92,7 +92,7 @@ autofocus('#id_username');
{% if not desktop_sso_dispatch %}
{% endif %}
diff --git a/zerver/views/__init__.py b/zerver/views/__init__.py
index aef86ae525..4da41bf2d9 100644
--- a/zerver/views/__init__.py
+++ b/zerver/views/__init__.py
@@ -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)
diff --git a/zproject/local_settings.py b/zproject/local_settings.py
index 2b5e0c3b49..b122ce82b7 100644
--- a/zproject/local_settings.py
+++ b/zproject/local_settings.py
@@ -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"
diff --git a/zproject/urls.py b/zproject/urls.py
index d8e99387e3..fbeca190d7 100644
--- a/zproject/urls.py
+++ b/zproject/urls.py
@@ -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'}),