mirror of
https://github.com/valitydev/redash.git
synced 2024-11-06 17:15:17 +00:00
Use flask-talisman for handling backend response headers (#3404)
* Normalize Flask initialization API use. * Use Flask-Talisman. * Enable HSTS when HTTPS is enforced. * More details about how CSP is formatted and write CSP directives as a string. * Use CSP frame-ancestors directive and not X-Frame-Options for embedable endpoints. * Add link to flask-talisman docs. * set remember_token cookie to be HTTP-Only and Secure * Reorganize secret key configuration to be forward thinking and backward compatible.
This commit is contained in:
parent
77c53130a4
commit
712fc63f93
@ -5,7 +5,6 @@ import urllib
|
||||
|
||||
import redis
|
||||
from flask import Flask, current_app
|
||||
from flask_sslify import SSLify
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
from werkzeug.routing import BaseConverter
|
||||
from statsd import StatsClient
|
||||
@ -98,11 +97,11 @@ class SlugConverter(BaseConverter):
|
||||
|
||||
|
||||
def create_app():
|
||||
from redash import authentication, extensions, handlers
|
||||
from redash import authentication, extensions, handlers, security
|
||||
from redash.handlers.webpack import configure_webpack
|
||||
from redash.handlers import chrome_logger
|
||||
from redash.models import db, users
|
||||
from redash.metrics.request import provision_app
|
||||
from redash.metrics import request as request_metrics
|
||||
from redash.utils import sentry
|
||||
|
||||
sentry.init()
|
||||
@ -116,14 +115,12 @@ def create_app():
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, settings.PROXIES_COUNT)
|
||||
app.url_map.converters['org_slug'] = SlugConverter
|
||||
|
||||
if settings.ENFORCE_HTTPS:
|
||||
SSLify(app, skips=['ping'])
|
||||
|
||||
# configure our database
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = settings.SQLALCHEMY_DATABASE_URI
|
||||
app.config.update(settings.all_settings())
|
||||
|
||||
provision_app(app)
|
||||
security.init_app(app)
|
||||
request_metrics.init_app(app)
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
mail.init_app(app)
|
||||
@ -131,7 +128,7 @@ def create_app():
|
||||
limiter.init_app(app)
|
||||
handlers.init_app(app)
|
||||
configure_webpack(app)
|
||||
extensions.init_extensions(app)
|
||||
extensions.init_app(app)
|
||||
chrome_logger.init_app(app)
|
||||
users.init_app(app)
|
||||
|
||||
|
@ -244,7 +244,6 @@ def init_app(app):
|
||||
login_manager.init_app(app)
|
||||
login_manager.anonymous_user = models.AnonymousUser
|
||||
|
||||
app.secret_key = settings.COOKIE_SECRET
|
||||
app.register_blueprint(google_oauth.blueprint)
|
||||
app.register_blueprint(saml_auth.blueprint)
|
||||
app.register_blueprint(remote_user_auth.blueprint)
|
||||
|
@ -4,12 +4,11 @@ from flask import render_template
|
||||
from redash import settings
|
||||
from redash.tasks import send_mail
|
||||
from redash.utils import base_url
|
||||
from redash.models import User
|
||||
# noinspection PyUnresolvedReferences
|
||||
from itsdangerous import URLSafeTimedSerializer, SignatureExpired, BadSignature
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
serializer = URLSafeTimedSerializer(settings.COOKIE_SECRET)
|
||||
serializer = URLSafeTimedSerializer(settings.SECRET_KEY)
|
||||
|
||||
|
||||
def invite_token(user):
|
||||
|
@ -2,7 +2,7 @@ import os
|
||||
from pkg_resources import iter_entry_points, resource_isdir, resource_listdir
|
||||
|
||||
|
||||
def init_extensions(app):
|
||||
def init_app(app):
|
||||
"""
|
||||
Load the Redash extensions for the given Redash Flask app.
|
||||
"""
|
||||
|
@ -5,9 +5,11 @@ from redash.handlers.api import api
|
||||
from redash.handlers.base import routes
|
||||
from redash.monitor import get_status
|
||||
from redash.permissions import require_super_admin
|
||||
from redash.security import talisman
|
||||
|
||||
|
||||
@routes.route('/ping', methods=['GET'])
|
||||
@talisman(force_https=False)
|
||||
def ping():
|
||||
return 'PONG.'
|
||||
|
||||
|
@ -6,10 +6,11 @@ from redash import models, serializers
|
||||
from redash.handlers.base import (BaseResource, get_object_or_404, paginate,
|
||||
filter_by_tags,
|
||||
order_results as _order_results)
|
||||
from redash.serializers import serialize_dashboard
|
||||
from redash.permissions import (can_modify, require_admin_or_owner,
|
||||
require_object_modify_permission,
|
||||
require_permission)
|
||||
from redash.security import csp_allows_embeding
|
||||
from redash.serializers import serialize_dashboard
|
||||
from sqlalchemy.orm.exc import StaleDataError
|
||||
|
||||
|
||||
@ -235,6 +236,8 @@ class DashboardResource(BaseResource):
|
||||
|
||||
|
||||
class PublicDashboardResource(BaseResource):
|
||||
decorators = BaseResource.decorators + [csp_allows_embeding]
|
||||
|
||||
def get(self, token):
|
||||
"""
|
||||
Retrieve a public dashboard.
|
||||
|
@ -9,10 +9,12 @@ from redash.handlers import routes
|
||||
from redash.handlers.base import (get_object_or_404, org_scoped_rule,
|
||||
record_event)
|
||||
from redash.handlers.static import render_index
|
||||
from redash.security import csp_allows_embeding
|
||||
|
||||
|
||||
@routes.route(org_scoped_rule('/embed/query/<query_id>/visualization/<visualization_id>'), methods=['GET'])
|
||||
@login_required
|
||||
@csp_allows_embeding
|
||||
def embed(query_id, visualization_id, org_slug=None):
|
||||
record_event(current_org, current_user._get_current_object(), {
|
||||
'action': 'view',
|
||||
@ -22,12 +24,12 @@ def embed(query_id, visualization_id, org_slug=None):
|
||||
'embed': True,
|
||||
'referer': request.headers.get('Referer')
|
||||
})
|
||||
|
||||
return render_index()
|
||||
|
||||
|
||||
@routes.route(org_scoped_rule('/public/dashboards/<token>'), methods=['GET'])
|
||||
@login_required
|
||||
@csp_allows_embeding
|
||||
def public_dashboard(token, org_slug=None):
|
||||
if current_user.is_api_user():
|
||||
dashboard = current_user.object
|
||||
|
@ -1,7 +1,4 @@
|
||||
import os
|
||||
|
||||
from flask import current_app, render_template, safe_join, send_file
|
||||
from werkzeug.exceptions import NotFound
|
||||
from flask import render_template, safe_join, send_file
|
||||
|
||||
from flask_login import login_required
|
||||
from redash import settings
|
||||
|
@ -45,7 +45,7 @@ def calculate_metrics_on_exception(error):
|
||||
calculate_metrics(MockResponse(500, '?', -1))
|
||||
|
||||
|
||||
def provision_app(app):
|
||||
def init_app(app):
|
||||
app.before_request(record_requets_start_time)
|
||||
app.after_request(calculate_metrics)
|
||||
app.teardown_request(calculate_metrics_on_exception)
|
||||
|
@ -74,7 +74,7 @@ class DataSource(BelongsToOrgMixin, db.Model):
|
||||
|
||||
name = Column(db.String(255))
|
||||
type = Column(db.String(255))
|
||||
options = Column('encrypted_options', ConfigurationContainer.as_mutable(EncryptedConfiguration(db.Text, settings.SECRET_KEY, FernetEngine)))
|
||||
options = Column('encrypted_options', ConfigurationContainer.as_mutable(EncryptedConfiguration(db.Text, settings.DATASOURCE_SECRET_KEY, FernetEngine)))
|
||||
queue_name = Column(db.String(255), default="queries")
|
||||
scheduled_queue_name = Column(db.String(255), default="scheduled_queries")
|
||||
created_at = Column(db.DateTime(True), default=db.func.now())
|
||||
|
43
redash/security.py
Normal file
43
redash/security.py
Normal file
@ -0,0 +1,43 @@
|
||||
import functools
|
||||
from flask_talisman import talisman
|
||||
|
||||
from redash import settings
|
||||
|
||||
|
||||
talisman = talisman.Talisman()
|
||||
|
||||
|
||||
def csp_allows_embeding(fn):
|
||||
|
||||
@functools.wraps(fn)
|
||||
def decorated(*args, **kwargs):
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
embedable_csp = talisman.content_security_policy + "frame-ancestors *;"
|
||||
return talisman(
|
||||
content_security_policy=embedable_csp,
|
||||
frame_options=None,
|
||||
)(decorated)
|
||||
|
||||
|
||||
def init_app(app):
|
||||
talisman.init_app(
|
||||
app,
|
||||
feature_policy=settings.FEATURE_POLICY,
|
||||
force_https=settings.ENFORCE_HTTPS,
|
||||
force_https_permanent=settings.ENFORCE_HTTPS_PERMANENT,
|
||||
force_file_save=settings.ENFORCE_FILE_SAVE,
|
||||
frame_options=settings.FRAME_OPTIONS,
|
||||
frame_options_allow_from=settings.FRAME_OPTIONS_ALLOW_FROM,
|
||||
strict_transport_security=settings.HSTS_ENABLED,
|
||||
strict_transport_security_preload=settings.HSTS_PRELOAD,
|
||||
strict_transport_security_max_age=settings.HSTS_MAX_AGE,
|
||||
strict_transport_security_include_subdomains=settings.HSTS_INCLUDE_SUBDOMAINS,
|
||||
content_security_policy=settings.CONTENT_SECURITY_POLICY,
|
||||
content_security_policy_report_uri=settings.CONTENT_SECURITY_POLICY_REPORT_URI,
|
||||
content_security_policy_report_only=settings.CONTENT_SECURITY_POLICY_REPORT_ONLY,
|
||||
content_security_policy_nonce_in=settings.CONTENT_SECURITY_POLICY_NONCE_IN,
|
||||
referrer_policy=settings.REFERRER_POLICY,
|
||||
session_cookie_secure=settings.SESSION_COOKIE_SECURE,
|
||||
session_cookie_http_only=settings.SESSION_COOKIE_HTTPONLY,
|
||||
)
|
@ -1,5 +1,6 @@
|
||||
import os
|
||||
from funcy import distinct, remove
|
||||
from flask_talisman import talisman
|
||||
|
||||
from .helpers import fix_assets_path, array_from_string, parse_boolean, int_or_none, set_from_string
|
||||
from .organization import DATE_FORMAT
|
||||
@ -15,7 +16,6 @@ def all_settings():
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
REDIS_URL = os.environ.get('REDASH_REDIS_URL', os.environ.get('REDIS_URL', "redis://localhost:6379/0"))
|
||||
PROXIES_COUNT = int(os.environ.get('REDASH_PROXIES_COUNT', "1"))
|
||||
|
||||
@ -50,9 +50,86 @@ SCHEMAS_REFRESH_SCHEDULE = int(os.environ.get("REDASH_SCHEMAS_REFRESH_SCHEDULE",
|
||||
SCHEMAS_REFRESH_QUEUE = os.environ.get("REDASH_SCHEMAS_REFRESH_QUEUE", "celery")
|
||||
|
||||
AUTH_TYPE = os.environ.get("REDASH_AUTH_TYPE", "api_key")
|
||||
ENFORCE_HTTPS = parse_boolean(os.environ.get("REDASH_ENFORCE_HTTPS", "false"))
|
||||
INVITATION_TOKEN_MAX_AGE = int(os.environ.get("REDASH_INVITATION_TOKEN_MAX_AGE", 60 * 60 * 24 * 7))
|
||||
|
||||
# The secret key to use in the Flask app for various cryptographic features
|
||||
SECRET_KEY = os.environ.get("REDASH_COOKIE_SECRET", "c292a0a3aa32397cdb050e233733900f")
|
||||
# The secret key to use when encrypting data source options
|
||||
DATASOURCE_SECRET_KEY = os.environ.get('REDASH_SECRET_KEY', SECRET_KEY)
|
||||
|
||||
# Whether and how to redirect non-HTTP requests to HTTPS. Disabled by default.
|
||||
ENFORCE_HTTPS = parse_boolean(os.environ.get("REDASH_ENFORCE_HTTPS", "false"))
|
||||
ENFORCE_HTTPS_PERMANENT = parse_boolean(
|
||||
os.environ.get("REDASH_ENFORCE_HTTPS_PERMANENT", "false"))
|
||||
# Whether file downloads are enforced or not.
|
||||
ENFORCE_FILE_SAVE = parse_boolean(
|
||||
os.environ.get("REDASH_ENFORCE_FILE_SAVE", "true"))
|
||||
|
||||
# Whether to use secure cookies by default.
|
||||
COOKIES_SECURE = parse_boolean(
|
||||
os.environ.get("REDASH_COOKIES_SECURE", str(ENFORCE_HTTPS)))
|
||||
# Whether the session cookie is set to secure.
|
||||
SESSION_COOKIE_SECURE = parse_boolean(
|
||||
os.environ.get("REDASH_SESSION_COOKIE_SECURE") or str(COOKIES_SECURE))
|
||||
# Whether the session cookie is set HttpOnly.
|
||||
SESSION_COOKIE_HTTPONLY = parse_boolean(
|
||||
os.environ.get("REDASH_SESSION_COOKIE_HTTPONLY", "true"))
|
||||
# Whether the session cookie is set to secure.
|
||||
REMEMBER_COOKIE_SECURE = parse_boolean(
|
||||
os.environ.get("REDASH_REMEMBER_COOKIE_SECURE") or str(COOKIES_SECURE))
|
||||
# Whether the remember cookie is set HttpOnly.
|
||||
REMEMBER_COOKIE_HTTPONLY = parse_boolean(
|
||||
os.environ.get("REDASH_REMEMBER_COOKIE_HTTPONLY", "true"))
|
||||
|
||||
# Doesn't set X-Frame-Options by default since it's highly dependent
|
||||
# on the specific deployment.
|
||||
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
||||
# for more information.
|
||||
FRAME_OPTIONS = os.environ.get("REDASH_FRAME_OPTIONS", "deny")
|
||||
FRAME_OPTIONS_ALLOW_FROM = os.environ.get(
|
||||
"REDASH_FRAME_OPTIONS_ALLOW_FROM", "")
|
||||
|
||||
# Whether and how to send Strict-Transport-Security response headers.
|
||||
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
|
||||
# for more information.
|
||||
HSTS_ENABLED = parse_boolean(
|
||||
os.environ.get("REDASH_HSTS_ENABLED") or str(ENFORCE_HTTPS))
|
||||
HSTS_PRELOAD = parse_boolean(os.environ.get("REDASH_HSTS_PRELOAD", "false"))
|
||||
HSTS_MAX_AGE = int(
|
||||
os.environ.get("REDASH_HSTS_MAX_AGE", talisman.ONE_YEAR_IN_SECS))
|
||||
HSTS_INCLUDE_SUBDOMAINS = parse_boolean(
|
||||
os.environ.get("REDASH_HSTS_INCLUDE_SUBDOMAINS", "false"))
|
||||
|
||||
# Whether and how to send Content-Security-Policy response headers.
|
||||
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
||||
# for more information.
|
||||
# Overriding this value via an environment variables requires setting it
|
||||
# as a string in the general CSP format of a semicolon separated list of
|
||||
# individual CSP directives, see https://github.com/GoogleCloudPlatform/flask-talisman#example-7
|
||||
# for more information. E.g.:
|
||||
CONTENT_SECURITY_POLICY = os.environ.get(
|
||||
"REDASH_CONTENT_SECURITY_POLICY",
|
||||
"default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; font-src 'self' data:; img-src 'self' http: https: data:; object-src 'none'; frame-ancestors 'none';"
|
||||
)
|
||||
CONTENT_SECURITY_POLICY_REPORT_URI = os.environ.get(
|
||||
"REDASH_CONTENT_SECURITY_POLICY_REPORT_URI", "")
|
||||
CONTENT_SECURITY_POLICY_REPORT_ONLY = parse_boolean(
|
||||
os.environ.get("REDASH_CONTENT_SECURITY_POLICY_REPORT_ONLY", "false"))
|
||||
CONTENT_SECURITY_POLICY_NONCE_IN = array_from_string(
|
||||
os.environ.get("REDASH_CONTENT_SECURITY_POLICY_NONCE_IN", ""))
|
||||
|
||||
# Whether and how to send Referrer-Policy response headers. Defaults to
|
||||
# 'strict-origin-when-cross-origin'.
|
||||
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
|
||||
# for more information.
|
||||
REFERRER_POLICY = os.environ.get(
|
||||
"REDASH_REFERRER_POLICY", "strict-origin-when-cross-origin")
|
||||
# Whether and how to send Feature-Policy response headers. Defaults to
|
||||
# an empty value.
|
||||
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy
|
||||
# for more information.
|
||||
FEATURE_POLICY = os.environ.get("REDASH_REFERRER_POLICY", "")
|
||||
|
||||
MULTI_ORG = parse_boolean(os.environ.get("REDASH_MULTI_ORG", "false"))
|
||||
|
||||
GOOGLE_CLIENT_ID = os.environ.get("REDASH_GOOGLE_CLIENT_ID", "")
|
||||
@ -111,9 +188,6 @@ LDAP_SEARCH_DN = os.environ.get('REDASH_LDAP_SEARCH_DN', os.environ.get('REDASH_
|
||||
STATIC_ASSETS_PATH = fix_assets_path(os.environ.get("REDASH_STATIC_ASSETS_PATH", "../client/dist/"))
|
||||
|
||||
JOB_EXPIRY_TIME = int(os.environ.get("REDASH_JOB_EXPIRY_TIME", 3600 * 12))
|
||||
COOKIE_SECRET = os.environ.get("REDASH_COOKIE_SECRET", "c292a0a3aa32397cdb050e233733900f")
|
||||
SESSION_COOKIE_SECURE = parse_boolean(os.environ.get("REDASH_SESSION_COOKIE_SECURE") or str(ENFORCE_HTTPS))
|
||||
SECRET_KEY = os.environ.get('REDASH_SECRET_KEY', COOKIE_SECRET)
|
||||
|
||||
LOG_LEVEL = os.environ.get("REDASH_LOG_LEVEL", "INFO")
|
||||
LOG_STDOUT = parse_boolean(os.environ.get('REDASH_LOG_STDOUT', 'false'))
|
||||
|
@ -15,7 +15,7 @@ requests-oauthlib>=0.6.2,<1.2.0
|
||||
Flask-SQLAlchemy==2.3.2
|
||||
Flask-Migrate==2.0.1
|
||||
flask-mail==0.9.1
|
||||
flask-sslify==0.1.5
|
||||
flask-talisman==0.6.0
|
||||
Flask-Limiter==0.9.3
|
||||
passlib==1.6.2
|
||||
aniso8601==1.1.0
|
||||
|
@ -3,10 +3,12 @@
|
||||
FLAG="/var/log/generate_secrets.log"
|
||||
if [ ! -f $FLAG ]; then
|
||||
COOKIE_SECRET=$(pwgen -1s 32)
|
||||
SECRET_KEY=$(pwgen -1s 32)
|
||||
POSTGRES_PASSWORD=$(pwgen -1s 32)
|
||||
REDASH_DATABASE_URL="postgresql:\/\/postgres:$POSTGRES_PASSWORD@postgres\/postgres"
|
||||
|
||||
sed -i "s/REDASH_COOKIE_SECRET=.*/REDASH_COOKIE_SECRET=$COOKIE_SECRET/g" /opt/redash/env
|
||||
sed -i "s/REDASH_SECRET_KEY=.*/REDASH_SECRET_KEY=$SECRET_KEY/g" /opt/redash/env
|
||||
sed -i "s/POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$POSTGRES_PASSWORD/g" /opt/redash/env
|
||||
sed -i "s/REDASH_DATABASE_URL=.*/REDASH_DATABASE_URL=$REDASH_DATABASE_URL/g" /opt/redash/env
|
||||
|
||||
|
@ -6,7 +6,7 @@ REDASH_BASE_PATH=/opt/redash
|
||||
|
||||
install_docker(){
|
||||
# Install Docker
|
||||
sudo apt-get update
|
||||
sudo apt-get update
|
||||
sudo apt-get -yy install apt-transport-https ca-certificates curl software-properties-common wget pwgen
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
|
||||
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||
@ -38,6 +38,7 @@ create_config() {
|
||||
fi
|
||||
|
||||
COOKIE_SECRET=$(pwgen -1s 32)
|
||||
SECRET_KEY=$(pwgen -1s 32)
|
||||
POSTGRES_PASSWORD=$(pwgen -1s 32)
|
||||
REDASH_DATABASE_URL="postgresql://postgres:${POSTGRES_PASSWORD}@postgres/postgres"
|
||||
|
||||
@ -46,6 +47,7 @@ create_config() {
|
||||
echo "REDASH_REDIS_URL=redis://redis:6379/0" >> $REDASH_BASE_PATH/env
|
||||
echo "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" >> $REDASH_BASE_PATH/env
|
||||
echo "REDASH_COOKIE_SECRET=$COOKIE_SECRET" >> $REDASH_BASE_PATH/env
|
||||
echo "REDASH_SECRET_KEY=$SECRET_KEY" >> $REDASH_BASE_PATH/env
|
||||
echo "REDASH_DATABASE_URL=$REDASH_DATABASE_URL" >> $REDASH_BASE_PATH/env
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
from funcy import pairwise
|
||||
from tests import BaseTestCase
|
||||
|
||||
from redash.models import DataSource, Query
|
||||
from redash.models import DataSource
|
||||
|
||||
|
||||
class TestDataSourceGetSchema(BaseTestCase):
|
||||
|
@ -2,6 +2,15 @@ from tests import BaseTestCase
|
||||
from redash.models import db
|
||||
|
||||
|
||||
class TestUnembedables(BaseTestCase):
|
||||
def test_not_embedable(self):
|
||||
query = self.factory.create_query()
|
||||
res = self.make_request('get', '/api/queries/{0}'.format(query.id))
|
||||
self.assertEquals(res.status_code, 200)
|
||||
self.assertIn("frame-ancestors 'none'", res.headers['Content-Security-Policy'])
|
||||
self.assertEqual(res.headers['X-Frame-Options'], 'deny')
|
||||
|
||||
|
||||
class TestEmbedVisualization(BaseTestCase):
|
||||
def test_sucesss(self):
|
||||
vis = self.factory.create_visualization()
|
||||
@ -10,6 +19,8 @@ class TestEmbedVisualization(BaseTestCase):
|
||||
|
||||
res = self.make_request("get", "/embed/query/{}/visualization/{}".format(vis.query_rel.id, vis.id), is_json=False)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertIn('frame-ancestors *', res.headers['Content-Security-Policy'])
|
||||
self.assertNotIn("X-Frame-Options", res.headers)
|
||||
|
||||
# TODO: bring back?
|
||||
# def test_parameters_on_embeds(self):
|
||||
@ -49,6 +60,8 @@ class TestPublicDashboard(BaseTestCase):
|
||||
|
||||
res = self.make_request('get', '/public/dashboards/{}'.format(api_key.api_key), user=False, is_json=False)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertIn('frame-ancestors *', res.headers['Content-Security-Policy'])
|
||||
self.assertNotIn("X-Frame-Options", res.headers)
|
||||
|
||||
def test_works_for_logged_in_user(self):
|
||||
dashboard = self.factory.create_dashboard()
|
||||
@ -72,6 +85,7 @@ class TestPublicDashboard(BaseTestCase):
|
||||
# def test_token_doesnt_belong_to_dashboard(self):
|
||||
# pass
|
||||
|
||||
|
||||
class TestAPIPublicDashboard(BaseTestCase):
|
||||
def test_success(self):
|
||||
dashboard = self.factory.create_dashboard()
|
||||
@ -79,6 +93,8 @@ class TestAPIPublicDashboard(BaseTestCase):
|
||||
|
||||
res = self.make_request('get', '/api/dashboards/public/{}'.format(api_key.api_key), user=False, is_json=False)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertIn('frame-ancestors *', res.headers['Content-Security-Policy'])
|
||||
self.assertNotIn("X-Frame-Options", res.headers)
|
||||
|
||||
def test_works_for_logged_in_user(self):
|
||||
dashboard = self.factory.create_dashboard()
|
||||
|
@ -1,6 +1,5 @@
|
||||
from tests import BaseTestCase
|
||||
from redash import models
|
||||
from redash.models import db
|
||||
|
||||
|
||||
class TestQueryFavoriteResource(BaseTestCase):
|
||||
def test_favorite(self):
|
||||
|
@ -19,8 +19,7 @@ class TestQueryResourceGet(BaseTestCase):
|
||||
self.assertResponseEqual(expected, rv.json)
|
||||
|
||||
def test_get_all_queries(self):
|
||||
queries = [self.factory.create_query() for _ in range(10)]
|
||||
|
||||
[self.factory.create_query() for _ in range(10)]
|
||||
rv = self.make_request('get', '/api/queries')
|
||||
|
||||
self.assertEquals(rv.status_code, 200)
|
||||
|
@ -13,14 +13,14 @@ class TestOrganizationSettings(BaseTestCase):
|
||||
updated_org = Organization.get_by_slug(self.factory.org.slug)
|
||||
self.assertEqual(rv.json['settings']['auth_password_login_enabled'], True)
|
||||
self.assertEqual(updated_org.settings['settings']['auth_password_login_enabled'], True)
|
||||
|
||||
|
||||
def test_updates_google_apps_domains(self):
|
||||
admin = self.factory.create_admin()
|
||||
domains = ['example.com']
|
||||
rv = self.make_request('post', '/api/settings/organization', data={'auth_google_apps_domains': domains}, user=admin)
|
||||
updated_org = Organization.get_by_slug(self.factory.org.slug)
|
||||
self.assertEqual(updated_org.google_apps_domains, domains)
|
||||
|
||||
|
||||
def test_get_returns_google_appas_domains(self):
|
||||
admin = self.factory.create_admin()
|
||||
domains = ['example.com']
|
||||
@ -28,4 +28,3 @@ class TestOrganizationSettings(BaseTestCase):
|
||||
|
||||
rv = self.make_request('get', '/api/settings/organization', user=admin)
|
||||
self.assertEqual(rv.json['settings']['auth_google_apps_domains'], domains)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user