Skip to content

Commit

Permalink
Merge pull request #480 from EverythingMe/feature/multiple-domains
Browse files Browse the repository at this point in the history
Feature: support for multiple domains with Google OAuth
  • Loading branch information
arikfr committed Jul 12, 2015
2 parents a692e3f + 7793f3b commit b671dd0
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 12 deletions.
13 changes: 12 additions & 1 deletion rd_ui/app/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<link rel="stylesheet" href="/styles/redash.css">
<link rel="stylesheet" href="/styles/login.css">
<!-- endbuild -->

<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
</head>
<body>

Expand All @@ -26,13 +30,20 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/"><strong>{{name}}</strong></a>
<a class="navbar-brand" href="/"><img src="/images/redash_icon_small.png"/></a>
</div>
</div>
</nav>

<div class="container">
<div class="row">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-warning" role="alert">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}

<div class="main">
{% if show_google_openid %}
Expand Down
4 changes: 2 additions & 2 deletions redash/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import logging

from flask import render_template, send_from_directory, make_response, request, jsonify, redirect, \
session, url_for, current_app
session, url_for, current_app, flash
from flask.ext.restful import Resource, abort
from flask_login import current_user, login_user, logout_user, login_required
import sqlparse
Expand Down Expand Up @@ -80,7 +80,7 @@ def login():
login_user(user, remember=remember)
return redirect(request.args.get('next') or '/')
except models.User.DoesNotExist:
pass
flash("Wrong username or password.")

return render_template("login.html",
name=settings.NAME,
Expand Down
31 changes: 23 additions & 8 deletions redash/google_oauth.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import logging
from flask.ext.login import login_user
import requests
from flask import redirect, url_for, Blueprint
from flask import redirect, url_for, Blueprint, flash
from flask_oauth import OAuth
from redash import models, settings

logger = logging.getLogger('google_oauth')
oauth = OAuth()

request_token_params = {'scope': 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile', 'response_type': 'code'}

if settings.GOOGLE_APPS_DOMAIN:
request_token_params['hd'] = settings.GOOGLE_APPS_DOMAIN
else:
if not settings.GOOGLE_APPS_DOMAIN:
logger.warning("No Google Apps domain defined, all Google accounts allowed.")

google = oauth.remote_app('google',
base_url='https://www.google.com/accounts/',
authorize_url='https://accounts.google.com/o/oauth2/auth',
request_token_url=None,
request_token_params=request_token_params,
request_token_params={
'scope': 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
'response_type': 'code'
},
access_token_url='https://accounts.google.com/o/oauth2/token',
access_token_method='POST',
access_token_params={'grant_type': 'authorization_code'},
Expand All @@ -31,7 +31,7 @@


def get_user_profile(access_token):
headers = {'Authorization': 'OAuth '+access_token}
headers = {'Authorization': 'OAuth {}'.format(access_token)}
response = requests.get('https://www.googleapis.com/oauth2/v1/userinfo', headers=headers)

if response.status_code == 401:
Expand All @@ -41,9 +41,17 @@ def get_user_profile(access_token):
return response.json()


def verify_profile(profile):
if not settings.GOOGLE_APPS_DOMAIN:
return True

domain = profile['email'].split('@')[-1]
return domain in settings.GOOGLE_APPS_DOMAIN


def create_and_login_user(name, email):
try:
user_object = models.User.get(models.User.email == email)
user_object = models.User.get_by_email(email)
if user_object.name != name:
logger.debug("Updating user name (%r -> %r)", user_object.name, name)
user_object.name = name
Expand All @@ -70,10 +78,17 @@ def authorized(resp):

if access_token is None:
logger.warning("Access token missing in call back request.")
flash("Validation error. Please retry.")
return redirect(url_for('login'))

profile = get_user_profile(access_token)
if profile is None:
flash("Validation error. Please retry.")
return redirect(url_for('login'))

if not verify_profile(profile):
logger.warning("User tried to login with unauthorized domain name: %s", profile['email'])
flash("Your Google Apps domain name isn't allowed.")
return redirect(url_for('login'))

create_and_login_user(profile['name'], profile['email'])
Expand Down
6 changes: 5 additions & 1 deletion redash/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ def array_from_string(str):
return array


def set_from_string(str):
return set(array_from_string(str))


def parse_boolean(str):
return json.loads(str.lower())

Expand Down Expand Up @@ -60,7 +64,7 @@ def parse_boolean(str):

# Google Apps domain to allow access from; any user with email in this Google Apps will be allowed
# access
GOOGLE_APPS_DOMAIN = os.environ.get("REDASH_GOOGLE_APPS_DOMAIN", "")
GOOGLE_APPS_DOMAIN = set_from_string(os.environ.get("REDASH_GOOGLE_APPS_DOMAIN", ""))

GOOGLE_CLIENT_ID = os.environ.get("REDASH_GOOGLE_CLIENT_ID", "")
GOOGLE_CLIENT_SECRET = os.environ.get("REDASH_GOOGLE_CLIENT_SECRET", "")
Expand Down

0 comments on commit b671dd0

Please sign in to comment.