diff --git a/src/metabase/public_settings/premium_features.clj b/src/metabase/public_settings/premium_features.clj index c9b1d08cfde0d7df82d1d4de92343fd4da2ee4c7..1fcbfc96e8670e83a0abdc233bf26f3365dec5e4 100644 --- a/src/metabase/public_settings/premium_features.clj +++ b/src/metabase/public_settings/premium_features.clj @@ -21,16 +21,21 @@ "Schema for a valid premium token. Must be 64 lower-case hex characters." #"^[0-9a-f]{64}$") -(def store-url - "URL to the MetaStore. Hardcoded by default but for development purposes you can use a local server. Specify the env - var `METASTORE_DEV_SERVER_URL`." +(def token-check-url + "Base URL to use for token checks. Hardcoded by default but for development purposes you can use a local server. + Specify the env var `METASTORE_DEV_SERVER_URL`." (or - ;; only enable the changing the store url during dev because we don't want people switching it out in production! + ;; only enable the changing the token check url during dev because we don't want people switching it out in production! (when config/is-dev? (some-> (env :metastore-dev-server-url) ;; remove trailing slashes (str/replace #"/$" ""))) - "https://store.metabase.com")) + "https://token-check.metabase.com")) + +(def store-url + "Store URL, used as a fallback for token checks and for fetching the list of cloud gateway IPs." + "https://store.metabase.com") + ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | TOKEN VALIDATION | @@ -43,9 +48,9 @@ ;; so we can't import the User model here. (db/count 'User :is_active true)) -(defn- token-status-url [token] +(defn- token-status-url [token base-url] (when (seq token) - (format "%s/api/%s/v2/status" store-url token))) + (format "%s/api/%s/v2/status" base-url token))) (def ^:private ^:const fetch-token-status-timeout-ms 10000) ; 10 seconds @@ -59,6 +64,14 @@ ;; don't explode in the future if we add more to the response! lol schema/Any schema/Any}) +(defn- fetch-token-and-parse-body + [token base-url] + (some-> (token-status-url token base-url) + (http/get {:query-params {:users (active-user-count) + :site-uuid (setting/get :site-uuid-for-premium-features-token-checks)}}) + :body + (json/parse-string keyword))) + (schema/defn ^:private fetch-token-status* :- TokenStatus "Fetch info about the validity of `token` from the MetaStore." [token :- ValidToken] @@ -69,22 +82,22 @@ (str (subs token 0 4) "..." (subs token 60 64)))) (deref (future - (try (some-> (token-status-url token) - (http/get {:query-params {:users (active-user-count) - :site-uuid (setting/get :site-uuid-for-premium-features-token-checks)}}) - :body - (json/parse-string keyword)) - ;; if there was an error fetching the token, log it and return a generic message about the - ;; token being invalid. This message will get displayed in the Settings page in the admin panel so - ;; we do not want something complicated - (catch Exception e - (log/error e (trs "Error fetching token status:")) - (let [body (u/ignore-exceptions (some-> (ex-data e) :body (json/parse-string keyword)))] - (or - body - {:valid false - :status (tru "Unable to validate token") - :error-details (.getMessage e)}))))) + (try (fetch-token-and-parse-body token token-check-url) + (catch Exception e1 + (log/error e1 (trs "Error fetching token status from {0}:" token-check-url)) + ;; Try the fallback URL, which was the default URL prior to 45.2 + (try (fetch-token-and-parse-body token store-url) + ;; if there was an error fetching the token from both the normal and fallback URLs, log the first error and + ;; return a generic message about the token being invalid. This message will get displayed in the Settings + ;; page in the admin panel so we do not want something complicated + (catch Exception e2 + (log/error e2 (trs "Error fetching token status from {0}:" store-url)) + (let [body (u/ignore-exceptions (some-> (ex-data e1) :body (json/parse-string keyword)))] + (or + body + {:valid false + :status (tru "Unable to validate token") + :error-details (.getMessage e1)}))))))) fetch-token-status-timeout-ms {:valid false :status (tru "Unable to validate token") diff --git a/test/metabase/public_settings/premium_features_test.clj b/test/metabase/public_settings/premium_features_test.clj index 186f871da9965ee38fb1ea8a6d892666cc08a07b..3c9ccd81ac98002e1ade752ae6050a9cec571376 100644 --- a/test/metabase/public_settings/premium_features_test.clj +++ b/test/metabase/public_settings/premium_features_test.clj @@ -36,7 +36,7 @@ (defn- token-status-response [token premium-features-response] (http-fake/with-fake-routes-in-isolation - {{:address (#'premium-features/token-status-url token) + {{:address (#'premium-features/token-status-url token @#'premium-features/token-check-url) :query-params {:users (str (#'premium-features/active-user-count)) :site-uuid (public-settings/site-uuid-for-premium-features-token-checks)}} (constantly premium-features-response)} @@ -77,7 +77,7 @@ :status "Unable to validate token" :error-details "network issues"} (premium-features/fetch-token-status (apply str (repeat 64 "b"))))))) - (testing "Only attempt the token once" + (testing "Only attempt the token twice (default and fallback URLs)" (let [call-count (atom 0) token (random-token)] (binding [clj-http.client/request (fn [& _] @@ -100,7 +100,7 @@ #'premium-features/enable-serialization?]] (testing (format "\n%s is false" (:name (meta has-feature?))) (is (not (has-feature?))))) - (is (= 1 + (is (= 2 @call-count)))))) (testing "With a valid token"