Skip to content
Snippets Groups Projects
download-permissions.cy.spec.js 8.34 KiB
Newer Older
import { SAMPLE_DB_ID, USER_GROUPS } from "e2e/support/cypress_data";
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import {
  ORDERS_DASHBOARD_ID,
  ORDERS_QUESTION_ID,
} from "e2e/support/cypress_sample_instance_data";
import {
  restore,
  modal,
  describeEE,
  assertPermissionForItem,
  downloadAndAssert,
} from "e2e/support/helpers";
const { ALL_USERS_GROUP, COLLECTION_GROUP, DATA_GROUP } = USER_GROUPS;
const {
  PRODUCTS_ID,
  ORDERS_ID,
  PEOPLE_ID,
  REVIEWS_ID,
  ACCOUNTS_ID,
const DATA_ACCESS_PERMISSION_INDEX = 0;
const DOWNLOAD_PERMISSION_INDEX = 2;

describeEE("scenarios > admin > permissions > data > downloads", () => {
  beforeEach(() => {
    restore();
    cy.signInAsAdmin();
    setTokenFeatures("all");
    // Restrict downloads for Collection and Data groups before each test so that they don't override All Users
    cy.updatePermissionsGraph({
      [COLLECTION_GROUP]: {
        [SAMPLE_DB_ID]: {
          download: { schemas: "none" },
        },
      },
      [DATA_GROUP]: {
        [SAMPLE_DB_ID]: {
          download: { schemas: "none" },
        },
      },
    });
  it("setting downloads permission UI flow should work", () => {
    cy.log("allows changing download results permission for a database");
    cy.visit(`/admin/permissions/data/database/${SAMPLE_DB_ID}`);
    modifyPermission("All Users", DOWNLOAD_PERMISSION_INDEX, "No");

    cy.button("Save changes").click();

    modal().within(() => {
      cy.findByText("Save permissions?");
      cy.findByText("Are you sure you want to do this?");
      cy.button("Yes").click();
    });

    assertPermissionForItem("All Users", DOWNLOAD_PERMISSION_INDEX, "No");
    cy.log("Make sure we can change download results permission for a table");
    sidebar().contains("Orders").click();

    modifyPermission("All Users", DOWNLOAD_PERMISSION_INDEX, "1 million rows");

    cy.button("Save changes").click();

    modal().within(() => {
      cy.findByText("Save permissions?");
      cy.findByText("Are you sure you want to do this?");
      cy.button("Yes").click();
    });

    assertPermissionForItem(
      DOWNLOAD_PERMISSION_INDEX,
      "1 million rows",
    );
  it("respects 'no download' permissions when 'All users' group data permissions are set to `Blocked` (metabase#22408)", () => {
    cy.visit(`/admin/permissions/data/database/${SAMPLE_DB_ID}`);
    modifyPermission("All Users", DATA_ACCESS_PERMISSION_INDEX, "Blocked");

    cy.button("Save changes").click();

    modal().within(() => {
      cy.findByText("Save permissions?");
      cy.findByText("Are you sure you want to do this?");
      cy.button("Yes").click();
    });

    // When data permissions are set to `Blocked`, download permissions are automatically revoked
    assertPermissionForItem("All Users", DOWNLOAD_PERMISSION_INDEX, "No");
    // Normal user belongs to both "data" and "collection" groups.
    // They both have restricted downloads so this user shouldn't have the right to download anything.
    cy.signInAsNormalUser();
    visitQuestion(ORDERS_QUESTION_ID);
    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
    cy.findByText("Showing first 2,000 rows");
    cy.icon("download").should("not.exist");

  it("restricts users from downloading questions", () => {
    // Restrict downloads for All Users
    cy.updatePermissionsGraph({
      [ALL_USERS_GROUP]: {
        [SAMPLE_DB_ID]: {
          download: { schemas: "none" },
        },
      },
    });

    cy.signInAsNormalUser();
    visitQuestion(ORDERS_QUESTION_ID);
    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
    cy.findByText("Showing first 2,000 rows");
    cy.icon("download").should("not.exist");
    visitDashboard(ORDERS_DASHBOARD_ID);

    cy.findByTestId("dashcard").within(() => {
      cy.findByTestId("legend-caption").realHover();
      cy.findByTestId("dashcard-menu").click();
    });

    popover().within(() => {
      cy.findByText("Edit question").should("be.visible");
      cy.findByText("Download results").should("not.exist");
  });

  it("limits users from downloading all results", () => {
    // Restrict downloads for All Users
    cy.updatePermissionsGraph({
      [ALL_USERS_GROUP]: {
        [SAMPLE_DB_ID]: {
          download: { schemas: "limited" },
        },
      },
    });

    cy.signInAsNormalUser();
    visitQuestion(ORDERS_QUESTION_ID);
      { fileType: "xlsx", questionId: ORDERS_QUESTION_ID },

  describe("native questions", () => {
    beforeEach(() => {
      cy.intercept("POST", "/api/dataset").as("dataset");

      cy.createNativeQuestion(
        {
          name: "Native Orders",
          native: {
            query: "select * from orders",
          },
        },
        { wrapId: true, idAlias: "nativeQuestionId" },
      );
    });

    it("lets user download results from native queries", () => {
      cy.signInAsNormalUser();

      cy.get("@nativeQuestionId").then(id => {
        visitQuestion(id);

        downloadAndAssert(
          { fileType: "xlsx", questionId: id },
          assertSheetRowsCount(18760),
        );

        // Make sure we can download results from an ad-hoc nested query based on a native question
        cy.findByText("Explore results").click();
        cy.wait("@dataset");

        downloadAndAssert({ fileType: "xlsx" }, assertSheetRowsCount(18760));

        // Make sure we can download results from a native model
        cy.request("PUT", `/api/card/${id}`, { name: "Native Model" });

        visitQuestion(id);

        downloadAndAssert(
          { fileType: "xlsx", questionId: id },
          assertSheetRowsCount(18760),
        );
      });
    });

    it("prevents user from downloading a native question even if only one table doesn't have download permissions", () => {
      setDownloadPermissionsForProductsTable("none");

      cy.signInAsNormalUser();

      cy.get("@nativeQuestionId").then(id => {
        visitQuestion(id);

        cy.findByText("Showing first 2,000 rows");
        cy.icon("download").should("not.exist");

        // Ad-hoc nested query also shouldn't be downloadable
        cy.findByText("Explore results").click();
        cy.wait("@dataset");

        cy.findByText("Showing first 2,000 rows");
        cy.icon("download").should("not.exist");

        // Convert question to a model, which also shouldn't be downloadable
        cy.request("PUT", `/api/card/${id}`, { name: "Native Model" });

        visitQuestion(id);

        cy.findByText("Showing first 2,000 rows");
        cy.icon("download").should("not.exist");
      });
    });

    it("limits download results for a native question even if only one table has `limited` download permissions", () => {
      setDownloadPermissionsForProductsTable("limited");

      cy.signInAsNormalUser();

      cy.get("@nativeQuestionId").then(id => {
        visitQuestion(id);

        downloadAndAssert(
          { fileType: "xlsx", questionId: id },
          assertSheetRowsCount(10000),
        );

        // Ad-hoc nested query based on a native question should also have a download row limit
        cy.findByText("Explore results").click();
        cy.wait("@dataset");

        downloadAndAssert({ fileType: "xlsx" }, assertSheetRowsCount(10000));

        // Convert question to a model, which should also have a download row limit
        cy.request("PUT", `/api/card/${id}`, { name: "Native Model" });

        visitQuestion(id);

        downloadAndAssert(
          { fileType: "xlsx", questionId: id },
          assertSheetRowsCount(10000),
        );
      });
    });
  });

function setDownloadPermissionsForProductsTable(permission) {
  cy.updatePermissionsGraph({
    [ALL_USERS_GROUP]: {
      [SAMPLE_DB_ID]: {
        download: {
          schemas: {
            PUBLIC: {
              [PRODUCTS_ID]: permission,
              [ORDERS_ID]: "full",
              [PEOPLE_ID]: "full",
              [REVIEWS_ID]: "full",
              [ANALYTIC_EVENTS_ID]: "full",
              [FEEDBACK_ID]: "full",