diff --git a/redash/authentication/google_oauth.py b/redash/authentication/google_oauth.py index 1107d4281..e9458792e 100644 --- a/redash/authentication/google_oauth.py +++ b/redash/authentication/google_oauth.py @@ -4,7 +4,7 @@ import requests from authlib.integrations.flask_client import OAuth from flask import Blueprint, flash, redirect, request, session, url_for -from redash import models +from redash import models, settings from redash.authentication import ( create_and_login_user, get_next_path, @@ -29,6 +29,41 @@ def verify_profile(org, profile): return False +def get_user_profile(access_token, logger): + headers = {"Authorization": f"OAuth {access_token}"} + response = requests.get("https://www.googleapis.com/oauth2/v1/userinfo", headers=headers) + + if response.status_code == 401: + logger.warning("Failed getting user profile (response code 401).") + return None + + return response.json() + + +def build_redirect_uri(): + scheme = settings.GOOGLE_OAUTH_SCHEME_OVERRIDE or None + return url_for(".callback", _external=True, _scheme=scheme) + + +def build_next_path(org_slug=None): + next_path = request.args.get("next") + if not next_path: + if org_slug is None: + org_slug = session.get("org_slug") + + scheme = None + if settings.GOOGLE_OAUTH_SCHEME_OVERRIDE: + scheme = settings.GOOGLE_OAUTH_SCHEME_OVERRIDE + + next_path = url_for( + "redash.index", + org_slug=org_slug, + _external=True, + _scheme=scheme, + ) + return next_path + + def create_google_oauth_blueprint(app): oauth = OAuth(app) @@ -36,23 +71,12 @@ def create_google_oauth_blueprint(app): blueprint = Blueprint("google_oauth", __name__) CONF_URL = "https://accounts.google.com/.well-known/openid-configuration" - oauth = OAuth(app) oauth.register( name="google", server_metadata_url=CONF_URL, client_kwargs={"scope": "openid email profile"}, ) - def get_user_profile(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: - logger.warning("Failed getting user profile (response code 401).") - return None - - return response.json() - @blueprint.route("//oauth/google", endpoint="authorize_org") def org_login(org_slug): session["org_slug"] = current_org.slug @@ -60,9 +84,9 @@ def create_google_oauth_blueprint(app): @blueprint.route("/oauth/google", endpoint="authorize") def login(): - redirect_uri = url_for(".callback", _external=True) + redirect_uri = build_redirect_uri() - next_path = request.args.get("next", url_for("redash.index", org_slug=session.get("org_slug"))) + next_path = build_next_path() logger.debug("Callback url: %s", redirect_uri) logger.debug("Next is: %s", next_path) @@ -86,7 +110,7 @@ def create_google_oauth_blueprint(app): flash("Validation error. Please retry.") return redirect(url_for("redash.login")) - profile = get_user_profile(access_token) + profile = get_user_profile(access_token, logger) if profile is None: flash("Validation error. Please retry.") return redirect(url_for("redash.login")) @@ -110,7 +134,9 @@ def create_google_oauth_blueprint(app): if user is None: return logout_and_redirect_to_index() - unsafe_next_path = session.get("next_url") or url_for("redash.index", org_slug=org.slug) + unsafe_next_path = session.get("next_url") + if not unsafe_next_path: + unsafe_next_path = build_next_path(org.slug) next_path = get_next_path(unsafe_next_path) return redirect(next_path) diff --git a/redash/settings/__init__.py b/redash/settings/__init__.py index 5da10eac7..53fdfd340 100644 --- a/redash/settings/__init__.py +++ b/redash/settings/__init__.py @@ -136,6 +136,13 @@ FEATURE_POLICY = os.environ.get("REDASH_FEATURE_POLICY", "") MULTI_ORG = parse_boolean(os.environ.get("REDASH_MULTI_ORG", "false")) +# If Redash is behind a proxy it might sometimes receive a X-Forwarded-Proto of HTTP +# even if your actual Redash URL scheme is HTTPS. This will cause Flask to build +# the OAuth redirect URL incorrectly thus failing auth. This is especially common if +# you're behind a SSL/TCP configured AWS ELB or similar. +# This setting will force the URL scheme. +GOOGLE_OAUTH_SCHEME_OVERRIDE = os.environ.get("REDASH_GOOGLE_OAUTH_SCHEME_OVERRIDE", "") + GOOGLE_CLIENT_ID = os.environ.get("REDASH_GOOGLE_CLIENT_ID", "") GOOGLE_CLIENT_SECRET = os.environ.get("REDASH_GOOGLE_CLIENT_SECRET", "") GOOGLE_OAUTH_ENABLED = bool(GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET)