From 61c30c08d697dda3c81bc45f0ec04a18f5172e26 Mon Sep 17 00:00:00 2001
From: john-metabase <92878045+john-metabase@users.noreply.github.com>
Date: Mon, 15 Nov 2021 11:57:04 -0500
Subject: [PATCH] Move initial user setup from hash data to env var plus token
 (#18763)

---
 .../settings/components/SettingsSetupList.jsx |  2 +-
 frontend/src/metabase/services.js             |  1 +
 .../src/metabase/setup/components/Setup.jsx   | 21 +++++++++----------
 frontend/test/__runner__/backend.js           |  9 ++++++++
 .../onboarding/setup/setup.cy.spec.js         | 11 +---------
 src/metabase/api/setup.clj                    | 11 ++++++++++
 src/metabase/config.clj                       |  9 +++++++-
 test/metabase/api/setup_test.clj              | 16 ++++++++++++++
 8 files changed, 57 insertions(+), 23 deletions(-)

diff --git a/frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx b/frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx
index 4b99f2ea18c..01f537a4565 100644
--- a/frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx
+++ b/frontend/src/metabase/admin/settings/components/SettingsSetupList.jsx
@@ -85,7 +85,7 @@ export default class SettingsSetupList extends Component {
   async componentDidMount() {
     try {
       const tasks = await SetupApi.admin_checklist();
-      this.setState({ tasks: tasks });
+      this.setState({ tasks });
     } catch (e) {
       this.setState({ error: e });
     }
diff --git a/frontend/src/metabase/services.js b/frontend/src/metabase/services.js
index 1fe823f45d6..c52c5757add 100644
--- a/frontend/src/metabase/services.js
+++ b/frontend/src/metabase/services.js
@@ -393,6 +393,7 @@ export const SetupApi = {
   create: POST("/api/setup"),
   validate_db: POST("/api/setup/validate"),
   admin_checklist: GET("/api/setup/admin_checklist"),
+  user_defaults: GET("/api/setup/user_defaults"),
 };
 
 export const UserApi = {
diff --git a/frontend/src/metabase/setup/components/Setup.jsx b/frontend/src/metabase/setup/components/Setup.jsx
index baf6f8c9ddd..937c280ae3b 100644
--- a/frontend/src/metabase/setup/components/Setup.jsx
+++ b/frontend/src/metabase/setup/components/Setup.jsx
@@ -6,7 +6,6 @@ import { t } from "ttag";
 import { color } from "metabase/lib/colors";
 import { trackStructEvent } from "metabase/lib/analytics";
 import MetabaseSettings from "metabase/lib/settings";
-import { b64hash_to_utf8 } from "metabase/lib/encoding";
 
 import AddDatabaseHelpCard from "metabase/components/AddDatabaseHelpCard";
 import DriverWarning from "metabase/components/DriverWarning";
@@ -21,6 +20,9 @@ import UserStep from "./UserStep";
 import DatabaseConnectionStep from "./DatabaseConnectionStep";
 import PreferencesStep from "./PreferencesStep";
 import { AddDatabaseHelpCardHolder } from "./Setup.styled";
+
+import { SetupApi } from "metabase/services";
+
 import {
   COMPLETED_STEP_NUMBER,
   DATABASE_CONNECTION_STEP_NUMBER,
@@ -56,10 +58,10 @@ export default class Setup extends Component {
     trackStructEvent("Setup", "Welcome");
   }
 
-  componentDidMount() {
-    this.setDefaultLanguage();
-    this.setDefaultDetails();
+  async componentDidMount() {
     this.trackStepSeen();
+    this.setDefaultLanguage();
+    await this.setDefaultDetails();
   }
 
   setDefaultLanguage() {
@@ -81,14 +83,11 @@ export default class Setup extends Component {
     }
   }
 
-  setDefaultDetails() {
-    const { hash } = this.props.location;
-
-    try {
-      const userDetails = hash && JSON.parse(b64hash_to_utf8(hash));
+  async setDefaultDetails() {
+    const token = this.props.location.hash.replace(/^#/, "");
+    if (token) {
+      const userDetails = await SetupApi.user_defaults({ token });
       this.setState({ defaultDetails: userDetails });
-    } catch (e) {
-      this.setState({ defaultDetails: undefined });
     }
   }
 
diff --git a/frontend/test/__runner__/backend.js b/frontend/test/__runner__/backend.js
index bc9d7844878..21393b10e94 100644
--- a/frontend/test/__runner__/backend.js
+++ b/frontend/test/__runner__/backend.js
@@ -68,6 +68,15 @@ export const BackendResource = createSharedResource("BackendResource", {
                 process.env["ENTERPRISE_TOKEN"]) ||
               undefined,
             MB_FIELD_FILTER_OPERATORS_ENABLED: "true",
+            MB_USER_DEFAULTS: JSON.stringify({
+              token: "123456",
+              user: {
+                first_name: "Testy",
+                last_name: "McTestface",
+                email: "testy@metabase.test",
+                site_name: "Epic Team",
+              },
+            }),
           },
           stdio:
             process.env["DISABLE_LOGGING"] ||
diff --git a/frontend/test/metabase/scenarios/onboarding/setup/setup.cy.spec.js b/frontend/test/metabase/scenarios/onboarding/setup/setup.cy.spec.js
index 77f597cf153..36620367a60 100644
--- a/frontend/test/metabase/scenarios/onboarding/setup/setup.cy.spec.js
+++ b/frontend/test/metabase/scenarios/onboarding/setup/setup.cy.spec.js
@@ -179,16 +179,7 @@ describe("scenarios > setup", () => {
   });
 
   it("should allow pre-filling user details", () => {
-    const details = {
-      user: {
-        first_name: "Testy",
-        last_name: "McTestface",
-        email: "testy@metabase.test",
-        site_name: "Epic Team",
-      },
-    };
-
-    cy.visit(`/setup#${btoa(JSON.stringify(details))}`);
+    cy.visit(`/setup#123456`);
 
     cy.findByText("Welcome to Metabase");
     cy.findByText("Let's get started").click();
diff --git a/src/metabase/api/setup.clj b/src/metabase/api/setup.clj
index ec8b9d0dee7..fc280fecb7f 100644
--- a/src/metabase/api/setup.clj
+++ b/src/metabase/api/setup.clj
@@ -2,6 +2,7 @@
   (:require [compojure.core :refer [GET POST]]
             [metabase.api.common :as api]
             [metabase.api.database :as database-api :refer [DBEngineString]]
+            [metabase.config :as config]
             [metabase.driver :as driver]
             [metabase.email :as email]
             [metabase.events :as events]
@@ -256,5 +257,15 @@
   (api/check-superuser)
   (admin-checklist))
 
+;; User defaults endpoint
+
+(api/defendpoint GET "/user_defaults"
+  "Returns object containing default user details for initial setup, if configured,
+   and if the provided token value matches the token in the configuration value."
+  [token]
+  (let [{config-token :token :as defaults} (config/mb-user-defaults)]
+    (api/check-404 config-token)
+    (api/check-403 (= token config-token))
+    (dissoc defaults :token)))
 
 (api/define-routes)
diff --git a/src/metabase/config.clj b/src/metabase/config.clj
index d8e2eae2325..341ea67b88b 100644
--- a/src/metabase/config.clj
+++ b/src/metabase/config.clj
@@ -1,5 +1,6 @@
 (ns metabase.config
-  (:require [clojure.java.io :as io]
+  (:require [cheshire.core :as json]
+            [clojure.java.io :as io]
             [clojure.string :as str]
             [clojure.tools.logging :as log]
             [environ.core :as environ]
@@ -143,3 +144,9 @@
         "Environ will use values from it in preference to env var or Java system properties you've specified.\n"
         "You should delete it; it will be recreated as needed when switching to a branch still using Leiningen.\n"
         "See https://github.com/metabase/metabase/wiki/Migrating-from-Leiningen-to-tools.deps#custom-env-var-values for more details.")))
+
+(defn mb-user-defaults
+  "Default user details provided as a JSON string at launch time for first-user setup flow."
+  []
+  (when-let [user-json (environ/env :mb-user-defaults)]
+    (json/parse-string user-json true)))
diff --git a/test/metabase/api/setup_test.clj b/test/metabase/api/setup_test.clj
index b5a5afeeac3..2f6335292c3 100644
--- a/test/metabase/api/setup_test.clj
+++ b/test/metabase/api/setup_test.clj
@@ -363,3 +363,19 @@
               :tasks (for [task tasks]
                        (-> (select-keys task [:title :completed :triggered :is_next_step])
                            (update :title str)))})))))
+
+(deftest user-defaults-test
+  (testing "with no user defaults configured"
+    (mt/with-temp-env-var-value [mb-user-defaults nil]
+      (is (= "Not found." (http/client :get "setup/user_defaults")))))
+
+  (testing "with defaults containing no token"
+    (mt/with-temp-env-var-value [mb-user-defaults "{}"]
+      (is (= "Not found." (http/client :get "setup/user_defaults")))))
+
+  (testing "with valid configuration"
+    (mt/with-temp-env-var-value [mb-user-defaults "{\"token\":\"123456\",\"email\":\"john.doe@example.com\"}"]
+      (testing "with mismatched token"
+        (is (= "You don't have permissions to do that." (http/client :get "setup/user_defaults?token=987654"))))
+      (testing "with valid token"
+        (is (= {:email "john.doe@example.com"} (http/client :get "setup/user_defaults?token=123456")))))))
-- 
GitLab