diff --git a/client/app/pages/queries/components/QueryPageHeader.jsx b/client/app/pages/queries/components/QueryPageHeader.jsx
index b925e94e1..00310dec5 100644
--- a/client/app/pages/queries/components/QueryPageHeader.jsx
+++ b/client/app/pages/queries/components/QueryPageHeader.jsx
@@ -123,7 +123,7 @@ export default function QueryPageHeader({
},
{
showAPIKey: {
- isAvailable: !queryFlags.isNew,
+ isAvailable: !clientConfig.disablePublicUrls && !queryFlags.isNew,
title: "Show API Key",
onClick: openApiKeyDialog,
},
@@ -199,7 +199,7 @@ export default function QueryPageHeader({
{!queryFlags.isNew && (
-
diff --git a/client/cypress/integration/dashboard/sharing_spec.js b/client/cypress/integration/dashboard/sharing_spec.js
index 3ddcab8a1..5e73b54e5 100644
--- a/client/cypress/integration/dashboard/sharing_spec.js
+++ b/client/cypress/integration/dashboard/sharing_spec.js
@@ -9,6 +9,37 @@ describe("Dashboard Sharing", () => {
this.dashboardId = id;
this.dashboardUrl = `/dashboards/${id}`;
});
+ cy.updateOrgSettings({ disable_public_urls: false });
+ });
+
+ it("is unavailable when public urls feature is disabled", function() {
+ const queryData = {
+ query: "select 1",
+ };
+
+ const position = { autoHeight: false, sizeY: 6 };
+ createQueryAndAddWidget(this.dashboardId, queryData, { position })
+ .then(() => {
+ cy.visit(this.dashboardUrl);
+ return shareDashboard();
+ })
+ .then(secretAddress => {
+ // disable the feature
+ cy.updateOrgSettings({ disable_public_urls: true });
+
+ // check the feature is disabled
+ cy.visit(this.dashboardUrl);
+ cy.getByTestId("DashboardMoreButton").should("exist");
+ cy.getByTestId("OpenShareForm").should("not.exist");
+
+ cy.logout();
+ cy.visit(secretAddress);
+ cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting
+ cy.getByTestId("TableVisualization").should("not.exist");
+
+ cy.login();
+ cy.updateOrgSettings({ disable_public_urls: false });
+ });
});
it("is possible if all queries are safe", function() {
diff --git a/client/cypress/integration/embed/share_embed_spec.js b/client/cypress/integration/embed/share_embed_spec.js
index 520482979..fea7f0ecb 100644
--- a/client/cypress/integration/embed/share_embed_spec.js
+++ b/client/cypress/integration/embed/share_embed_spec.js
@@ -1,6 +1,43 @@
describe("Embedded Queries", () => {
beforeEach(() => {
cy.login();
+ cy.updateOrgSettings({ disable_public_urls: false });
+ });
+
+ it("is unavailable when public urls feature is disabled", () => {
+ cy.createQuery({ query: "select name from users order by name" }).then(query => {
+ cy.visit(`/queries/${query.id}/source`);
+ cy.getByTestId("ExecuteButton").click();
+ cy.getByTestId("QueryPageVisualizationTabs", { timeout: 10000 }).should("exist");
+ cy.clickThrough(`
+ QueryControlDropdownButton
+ ShowEmbedDialogButton
+ `);
+ cy.getByTestId("EmbedIframe")
+ .invoke("text")
+ .then(embedUrl => {
+ // disable the feature
+ cy.updateOrgSettings({ disable_public_urls: true });
+
+ // check the feature is disabled
+ cy.visit(`/queries/${query.id}/source`);
+ cy.getByTestId("QueryPageHeaderMoreButton").click();
+ cy.get(".ant-dropdown-menu-item")
+ .should("exist")
+ .should("not.contain", "Show API Key");
+ cy.getByTestId("QueryControlDropdownButton").click();
+ cy.get(".ant-dropdown-menu-item").should("exist");
+ cy.getByTestId("ShowEmbedDialogButton").should("not.exist");
+
+ cy.logout();
+ cy.visit(embedUrl);
+ cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting
+ cy.getByTestId("TableVisualization").should("not.exist");
+
+ cy.login();
+ cy.updateOrgSettings({ disable_public_urls: false });
+ });
+ });
});
it("can be shared without parameters", () => {
diff --git a/client/cypress/support/redash-api/index.js b/client/cypress/support/redash-api/index.js
index 1292f9e1a..e9bc0ace6 100644
--- a/client/cypress/support/redash-api/index.js
+++ b/client/cypress/support/redash-api/index.js
@@ -165,3 +165,7 @@ Cypress.Commands.add("addDestinationSubscription", (alertId, destinationName) =>
return body;
});
});
+
+Cypress.Commands.add("updateOrgSettings", settings => {
+ return post({ url: "api/settings/organization", body: settings }).then(({ body }) => body);
+});
diff --git a/redash/handlers/authentication.py b/redash/handlers/authentication.py
index a4f1d2f87..bee4487e6 100644
--- a/redash/handlers/authentication.py
+++ b/redash/handlers/authentication.py
@@ -288,6 +288,7 @@ def client_config():
"hidePlotlyModeBar": current_org.get_setting(
"hide_plotly_mode_bar"
),
+ "disablePublicUrls": current_org.get_setting("disable_public_urls"),
"allowCustomJSVisualizations": settings.FEATURE_ALLOW_CUSTOM_JS_VISUALIZATIONS,
"autoPublishNamedQueries": settings.FEATURE_AUTO_PUBLISH_NAMED_QUERIES,
"extendedAlertOptions": settings.FEATURE_EXTENDED_ALERT_OPTIONS,
diff --git a/redash/handlers/dashboards.py b/redash/handlers/dashboards.py
index cfc710930..5aace124d 100644
--- a/redash/handlers/dashboards.py
+++ b/redash/handlers/dashboards.py
@@ -268,6 +268,9 @@ class PublicDashboardResource(BaseResource):
:param token: An API key for a public dashboard.
:>json array widgets: An array of arrays of :ref:`public widgets
`, corresponding to the rows and columns the widgets are displayed in
"""
+ if self.current_org.get_setting("disable_public_urls"):
+ abort(400, message="Public URLs are disabled.")
+
if not isinstance(self.current_user, models.ApiUser):
api_key = get_object_or_404(models.ApiKey.get_by_api_key, token)
dashboard = api_key.object
diff --git a/redash/settings/organization.py b/redash/settings/organization.py
index 782802a46..0181a7026 100644
--- a/redash/settings/organization.py
+++ b/redash/settings/organization.py
@@ -43,8 +43,9 @@ FEATURE_SHOW_PERMISSIONS_CONTROL = parse_boolean(
SEND_EMAIL_ON_FAILED_SCHEDULED_QUERIES = parse_boolean(
os.environ.get("REDASH_SEND_EMAIL_ON_FAILED_SCHEDULED_QUERIES", "false")
)
-HIDE_PLOTLY_MODE_BAR = parse_boolean(
- os.environ.get("HIDE_PLOTLY_MODE_BAR", "false")
+HIDE_PLOTLY_MODE_BAR = parse_boolean(os.environ.get("HIDE_PLOTLY_MODE_BAR", "false"))
+DISABLE_PUBLIC_URLS = parse_boolean(
+ os.environ.get("REDASH_DISABLE_PUBLIC_URLS", "false")
)
settings = {
@@ -69,4 +70,5 @@ settings = {
"feature_show_permissions_control": FEATURE_SHOW_PERMISSIONS_CONTROL,
"send_email_on_failed_scheduled_queries": SEND_EMAIL_ON_FAILED_SCHEDULED_QUERIES,
"hide_plotly_mode_bar": HIDE_PLOTLY_MODE_BAR,
+ "disable_public_urls": DISABLE_PUBLIC_URLS,
}