mirror of
https://github.com/getredash/redash.git
synced 2025-12-19 17:37:19 -05:00
Compare commits
5 Commits
23.09.0-de
...
v5.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de0089ceb9 | ||
|
|
fa92fec012 | ||
|
|
2d86305852 | ||
|
|
d6ba42613b | ||
|
|
c91b73d9e3 |
@@ -115,11 +115,11 @@ workflows:
|
|||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
- release/.*
|
- /release\/.*/
|
||||||
- build-docker-image:
|
- build-docker-image:
|
||||||
requires:
|
requires:
|
||||||
- unit-tests
|
- unit-tests
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- release/.*
|
- /release\/.*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "redash-client",
|
"name": "redash-client",
|
||||||
"version": "5.0.0",
|
"version": "5.0.1",
|
||||||
"description": "The frontend part of Redash.",
|
"description": "The frontend part of Redash.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from redash.query_runner import import_query_runners
|
|||||||
from redash.destinations import import_destinations
|
from redash.destinations import import_destinations
|
||||||
|
|
||||||
|
|
||||||
__version__ = '5.0.0'
|
__version__ = '5.0.1'
|
||||||
|
|
||||||
|
|
||||||
def setup_logging():
|
def setup_logging():
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ import time
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask import redirect, request, jsonify, url_for
|
from flask import redirect, request, jsonify, url_for
|
||||||
|
from werkzeug.exceptions import Unauthorized
|
||||||
|
|
||||||
from redash import models, settings
|
from redash import models, settings
|
||||||
|
from redash.settings.organization import settings as org_settings
|
||||||
|
from redash.authentication import jwt_auth
|
||||||
from redash.authentication.org_resolving import current_org
|
from redash.authentication.org_resolving import current_org
|
||||||
from redash.tasks import record_event
|
from redash.tasks import record_event
|
||||||
|
|
||||||
@@ -48,6 +51,21 @@ def load_user(user_id):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def request_loader(request):
|
||||||
|
user = None
|
||||||
|
if settings.AUTH_TYPE == 'hmac':
|
||||||
|
user = hmac_load_user_from_request(request)
|
||||||
|
elif settings.AUTH_TYPE == 'api_key':
|
||||||
|
user = api_key_load_user_from_request(request)
|
||||||
|
else:
|
||||||
|
logger.warning("Unknown authentication type ({}). Using default (HMAC).".format(settings.AUTH_TYPE))
|
||||||
|
user = hmac_load_user_from_request(request)
|
||||||
|
|
||||||
|
if org_settings['auth_jwt_login_enabled'] and user is None:
|
||||||
|
user = jwt_token_load_user_from_request(request)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
def hmac_load_user_from_request(request):
|
def hmac_load_user_from_request(request):
|
||||||
signature = request.args.get('signature')
|
signature = request.args.get('signature')
|
||||||
expires = float(request.args.get('expires') or 0)
|
expires = float(request.args.get('expires') or 0)
|
||||||
@@ -116,6 +134,40 @@ def api_key_load_user_from_request(request):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def jwt_token_load_user_from_request(request):
|
||||||
|
org = current_org._get_current_object()
|
||||||
|
|
||||||
|
payload = None
|
||||||
|
|
||||||
|
if org_settings['auth_jwt_auth_cookie_name']:
|
||||||
|
jwt_token = request.cookies.get(org_settings['auth_jwt_auth_cookie_name'], None)
|
||||||
|
elif org_settings['auth_jwt_auth_header_name']:
|
||||||
|
jwt_token = request.headers.get(org_settings['auth_jwt_auth_header_name'], None)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if jwt_token:
|
||||||
|
payload, token_is_valid = jwt_auth.verify_jwt_token(
|
||||||
|
jwt_token,
|
||||||
|
expected_issuer=org_settings['auth_jwt_auth_issuer'],
|
||||||
|
expected_audience=org_settings['auth_jwt_auth_audience'],
|
||||||
|
algorithms=org_settings['auth_jwt_auth_algorithms'],
|
||||||
|
public_certs_url=org_settings['auth_jwt_auth_public_certs_url'],
|
||||||
|
)
|
||||||
|
if not token_is_valid:
|
||||||
|
raise Unauthorized('Invalid JWT token')
|
||||||
|
|
||||||
|
if not payload:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = models.User.get_by_email_and_org(payload['email'], org)
|
||||||
|
except models.NoResultFound:
|
||||||
|
user = create_and_login_user(current_org, payload['email'], payload['email'])
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
def log_user_logged_in(app, user):
|
def log_user_logged_in(app, user):
|
||||||
event = {
|
event = {
|
||||||
'org_id': current_org.id,
|
'org_id': current_org.id,
|
||||||
@@ -168,14 +220,7 @@ def setup_authentication(app):
|
|||||||
app.register_blueprint(ldap_auth.blueprint)
|
app.register_blueprint(ldap_auth.blueprint)
|
||||||
|
|
||||||
user_logged_in.connect(log_user_logged_in)
|
user_logged_in.connect(log_user_logged_in)
|
||||||
|
login_manager.request_loader(request_loader)
|
||||||
if settings.AUTH_TYPE == 'hmac':
|
|
||||||
login_manager.request_loader(hmac_load_user_from_request)
|
|
||||||
elif settings.AUTH_TYPE == 'api_key':
|
|
||||||
login_manager.request_loader(api_key_load_user_from_request)
|
|
||||||
else:
|
|
||||||
logger.warning("Unknown authentication type ({}). Using default (HMAC).".format(settings.AUTH_TYPE))
|
|
||||||
login_manager.request_loader(hmac_load_user_from_request)
|
|
||||||
|
|
||||||
|
|
||||||
def create_and_login_user(org, name, email, picture=None):
|
def create_and_login_user(org, name, email, picture=None):
|
||||||
|
|||||||
65
redash/authentication/jwt_auth.py
Normal file
65
redash/authentication/jwt_auth.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import jwt
|
||||||
|
import requests
|
||||||
|
|
||||||
|
logger = logging.getLogger('jwt_auth')
|
||||||
|
|
||||||
|
|
||||||
|
def get_public_keys(url):
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
List of RSA public keys usable by PyJWT.
|
||||||
|
"""
|
||||||
|
key_cache = get_public_keys.key_cache
|
||||||
|
if url in key_cache:
|
||||||
|
return key_cache[url]
|
||||||
|
else:
|
||||||
|
r = requests.get(url)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()
|
||||||
|
if 'keys' in data:
|
||||||
|
public_keys = []
|
||||||
|
for key_dict in data['keys']:
|
||||||
|
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key_dict))
|
||||||
|
public_keys.append(public_key)
|
||||||
|
|
||||||
|
get_public_keys.key_cache[url] = public_keys
|
||||||
|
return public_keys
|
||||||
|
else:
|
||||||
|
get_public_keys.key_cache[url] = data
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
get_public_keys.key_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def verify_jwt_token(jwt_token, expected_issuer, expected_audience, algorithms, public_certs_url):
|
||||||
|
# https://developers.cloudflare.com/access/setting-up-access/validate-jwt-tokens/
|
||||||
|
# https://cloud.google.com/iap/docs/signed-headers-howto
|
||||||
|
# Loop through the keys since we can't pass the key set to the decoder
|
||||||
|
keys = get_public_keys(public_certs_url)
|
||||||
|
|
||||||
|
key_id = jwt.get_unverified_header(jwt_token).get('kid', '')
|
||||||
|
if key_id and isinstance(keys, dict):
|
||||||
|
keys = [keys.get(key_id)]
|
||||||
|
|
||||||
|
valid_token = False
|
||||||
|
payload = None
|
||||||
|
for key in keys:
|
||||||
|
try:
|
||||||
|
# decode returns the claims which has the email if you need it
|
||||||
|
payload = jwt.decode(
|
||||||
|
jwt_token,
|
||||||
|
key=key,
|
||||||
|
audience=expected_audience,
|
||||||
|
algorithms=algorithms
|
||||||
|
)
|
||||||
|
issuer = payload['iss']
|
||||||
|
if issuer != expected_issuer:
|
||||||
|
raise Exception('Wrong issuer: {}'.format(issuer))
|
||||||
|
valid_token = True
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
return payload, valid_token
|
||||||
@@ -17,11 +17,26 @@ SAML_LOGIN_ENABLED = SAML_METADATA_URL != ""
|
|||||||
|
|
||||||
DATE_FORMAT = os.environ.get("REDASH_DATE_FORMAT", "DD/MM/YY")
|
DATE_FORMAT = os.environ.get("REDASH_DATE_FORMAT", "DD/MM/YY")
|
||||||
|
|
||||||
|
JWT_LOGIN_ENABLED = parse_boolean(os.environ.get("REDASH_JWT_LOGIN_ENABLED", "false"))
|
||||||
|
JWT_AUTH_ISSUER = os.environ.get("REDASH_JWT_AUTH_ISSUER", "")
|
||||||
|
JWT_AUTH_PUBLIC_CERTS_URL = os.environ.get("REDASH_JWT_AUTH_PUBLIC_CERTS_URL", "")
|
||||||
|
JWT_AUTH_AUDIENCE = os.environ.get("REDASH_JWT_AUTH_AUDIENCE", "")
|
||||||
|
JWT_AUTH_ALGORITHMS = os.environ.get("REDASH_JWT_AUTH_ALGORITHMS", "HS256,RS256,ES256").split(',')
|
||||||
|
JWT_AUTH_COOKIE_NAME = os.environ.get("REDASH_JWT_AUTH_COOKIE_NAME", "")
|
||||||
|
JWT_AUTH_HEADER_NAME = os.environ.get("REDASH_JWT_AUTH_HEADER_NAME", "")
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
"auth_password_login_enabled": PASSWORD_LOGIN_ENABLED,
|
"auth_password_login_enabled": PASSWORD_LOGIN_ENABLED,
|
||||||
"auth_saml_enabled": SAML_LOGIN_ENABLED,
|
"auth_saml_enabled": SAML_LOGIN_ENABLED,
|
||||||
"auth_saml_entity_id": SAML_ENTITY_ID,
|
"auth_saml_entity_id": SAML_ENTITY_ID,
|
||||||
"auth_saml_metadata_url": SAML_METADATA_URL,
|
"auth_saml_metadata_url": SAML_METADATA_URL,
|
||||||
"auth_saml_nameid_format": SAML_NAMEID_FORMAT,
|
"auth_saml_nameid_format": SAML_NAMEID_FORMAT,
|
||||||
"date_format": DATE_FORMAT
|
"date_format": DATE_FORMAT,
|
||||||
|
"auth_jwt_login_enabled": JWT_LOGIN_ENABLED,
|
||||||
|
"auth_jwt_auth_issuer": JWT_AUTH_ISSUER,
|
||||||
|
"auth_jwt_auth_public_certs_url": JWT_AUTH_PUBLIC_CERTS_URL,
|
||||||
|
"auth_jwt_auth_audience": JWT_AUTH_AUDIENCE,
|
||||||
|
"auth_jwt_auth_algorithms": JWT_AUTH_ALGORITHMS,
|
||||||
|
"auth_jwt_auth_cookie_name": JWT_AUTH_COOKIE_NAME,
|
||||||
|
"auth_jwt_auth_header_name": JWT_AUTH_HEADER_NAME,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ wsgiref==0.1.2
|
|||||||
honcho==0.5.0
|
honcho==0.5.0
|
||||||
statsd==2.1.2
|
statsd==2.1.2
|
||||||
gunicorn==19.7.1
|
gunicorn==19.7.1
|
||||||
celery==3.1.25
|
celery==3.1.26.post2
|
||||||
jsonschema==2.4.0
|
jsonschema==2.4.0
|
||||||
RestrictedPython==3.6.0
|
RestrictedPython==3.6.0
|
||||||
pysaml2==4.5.0
|
pysaml2==4.5.0
|
||||||
@@ -44,6 +44,7 @@ semver==2.2.1
|
|||||||
xlsxwriter==0.9.3
|
xlsxwriter==0.9.3
|
||||||
pystache==0.5.4
|
pystache==0.5.4
|
||||||
parsedatetime==2.1
|
parsedatetime==2.1
|
||||||
|
PyJWT==1.6.4
|
||||||
cryptography==2.0.2
|
cryptography==2.0.2
|
||||||
simplejson==3.10.0
|
simplejson==3.10.0
|
||||||
ua-parser==0.7.3
|
ua-parser==0.7.3
|
||||||
|
|||||||
Reference in New Issue
Block a user