Settings related to checking premium token validity and which premium features it allows. | (ns metabase.public-settings.premium-features (:require [clj-http.client :as http] [clojure.core.memoize :as memoize] [clojure.spec.alpha :as s] [clojure.string :as str] [diehard.circuit-breaker :as dh.cb] [diehard.core :as dh] [environ.core :refer [env]] [malli.core :as mc] [metabase.api.common :as api] [metabase.config :as config] [metabase.models.setting :as setting :refer [defsetting]] [metabase.plugins.classloader :as classloader] [metabase.util :as u] [metabase.util.i18n :refer [deferred-tru trs tru]] [metabase.util.json :as json] [metabase.util.log :as log] [metabase.util.malli :as mu] [metabase.util.malli.schema :as ms] [metabase.util.string :as u.str] [toucan2.connection :as t2.conn] [toucan2.core :as t2])) |
(set! *warn-on-reflection* true) | |
Schema for a valid premium token. Must be 64 lower-case hex characters. | (def ^:private RemoteCheckedToken #"^[0-9a-f]{64}$") |
Similar to RemoteCheckedToken, but starts with 'airgap_'. | (def ^:private AirgapToken #"airgap_.+") |
(def ^:private TokenStr [:or [:re RemoteCheckedToken] [:re AirgapToken]]) | |
Base URL to use for token checks. Hardcoded by default but for development purposes you can use a local server.
Specify the env var | (def token-check-url (or ;; 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://token-check.metabase.com")) |
Store URL, used as a fallback for token checks and for fetching the list of cloud gateway IPs. | (def store-url "https://store.metabase.com") |
+----------------------------------------------------------------------------------------------------------------+ | TOKEN VALIDATION | +----------------------------------------------------------------------------------------------------------------+ | |
(declare premium-embedding-token) | |
Returns a count of users on the system let's prevent the DB from getting slammed with calls to get the active user count, we only really need one in flight at a time. | (let [f (fn [] {:post [(integer? %)]} (log/debug (u/colorize :yellow "GETTING ACTIVE USER COUNT!")) (assert ((requiring-resolve 'metabase.db/db-is-set-up?)) "Metabase DB is not yet set up") ;; force this to use a new Connection, it seems to be getting called in situations where the Connection ;; is from a different thread and is invalid by the time we get to use it (let [result (binding [t2.conn/*current-connectable* nil] (t2/count :model/User :is_active true :type :personal))] (log/debug (u/colorize :green "=>") result) result)) lock (Object.)] (defn- locking-active-user-count [] (locking lock (f)))) |
(defsetting active-users-count (deferred-tru "Number of active users") :visibility :admin :type :integer :audit :never :setter :none :default 0 :export? false :getter (fn [] (if-not ((requiring-resolve 'metabase.db/db-is-set-up?)) 0 (locking-active-user-count)))) | |
(defn- token-status-url [token base-url] (when (seq token) (format "%s/api/%s/v2/status" base-url token))) | |
Schema for a response from the token status API. | (def TokenStatus [:map [:valid :boolean] [:status [:string {:min 1}]] [:error-details {:optional true} [:maybe [:string {:min 1}]]] [:features {:optional true} [:sequential [:string {:min 1}]]] [:plan-alias {:optional true} :string] [:trial {:optional true} :boolean] [:valid-thru {:optional true} [:string {:min 1}]] [:max-users {:optional true} pos-int?] [:company {:optional true} [:string {:min 1}]]]) |
Amount of time to cache the status of a valid enterprise token before forcing a re-check. | (def ^:private ^:const token-status-cache-ttl (u/hours->ms 12)) |
Caches successful and 4XX API responses for 24 hours. 5XX errors, timeouts, etc. may be transient and will NOT be cached, but may trigger the store-circuit-breaker. | (def ^{:arglists '([token base-url site-uuid])} fetch-token-and-parse-body* (memoize/ttl ^{::memoize/args-fn (fn [[token base-url site-uuid]] [token base-url site-uuid])} (fn [token base-url site-uuid] (log/infof "Checking with the MetaStore to see whether token '%s' is valid..." (u.str/mask token)) (let [{:keys [body status] :as resp} (some-> (token-status-url token base-url) (http/get {:query-params {:users (active-users-count) :site-uuid site-uuid :mb-version (:tag config/mb-version-info)} :throw-exceptions false}))] (cond (http/success? resp) (some-> body json/decode+kw) (<= 400 status 499) (some-> body json/decode+kw) ;; exceptions are not cached. :else (throw (ex-info "An unknown error occurred when validating token." {:status status :body body}))))) :ttl/threshold token-status-cache-ttl)) |
(def ^:private store-circuit-breaker-config {;; if 10 requests within 10 seconds fail, open the circuit breaker. ;; (a lower threshold ratio wouldn't make sense here because successful results are cached, so as soon as we get ;; one successful response we're guaranteed to only get successes until cache expiration) :failure-threshold-ratio-in-period [10 10 (u/seconds->ms 10)] ;; after the circuit is opened, wait 30 seconds before making any more requests to the store :delay-ms (u/seconds->ms 30) ;; when the circuit breaker is half-open, one request will be permitted. if it's successful, return to normal. ;; otherwise we'll wait another 30 seconds. :success-threshold 1}) | |
A circuit breaker that short-circuits when requests to the API have repeatedly failed. This prevents a pathological scenario where the store has a temporary outage (long enough for the cache to expire) and then all instances everywhere fire off constant requests to get token status. Instead, execution will constantly fail instantly until the circuit breaker is closed. | (def ^:dynamic *store-circuit-breaker* (dh.cb/circuit-breaker store-circuit-breaker-config)) |
(def ^:private ^:const fetch-token-status-timeout-ms (u/seconds->ms 10)) | |
(defn- fetch-token-and-parse-body [token base-url site-uuid] (try (dh/with-circuit-breaker *store-circuit-breaker* (dh/with-timeout {:timeout-ms fetch-token-status-timeout-ms :interrupt? true} (try (fetch-token-and-parse-body* token base-url site-uuid) (catch Exception e (throw e))))) (catch dev.failsafe.TimeoutExceededException _e {:valid false :status (tru "Unable to validate token") :error-details (tru "Token validation timed out.")}) (catch dev.failsafe.CircuitBreakerOpenException _e {:valid false :status (tru "Unable to validate token") :error-details (tru "Token validation is currently unavailable.")}) ;; other exceptions are wrapped by Diehard in a FailsafeException. Unwrap them before rethrowing. (catch dev.failsafe.FailsafeException e (throw (.getCause e))))) | |
Airgap Tokens ;;;;;;;;;;;;;;;;;;;; | |
(declare decode-airgap-token) | |
(mu/defn max-users-allowed :- [:maybe pos-int?] "Returns the max users value from an airgapped key, or nil indicating there is no limt." [] (when-let [token (premium-embedding-token)] (when (str/starts-with? token "airgap_") (let [max-users (:max-users (decode-airgap-token token))] (when (pos? max-users) max-users))))) | |
Checks that, when in an airgap context, the allowed user count is acceptable. | (defn airgap-check-user-count [] (when-let [max-users (max-users-allowed)] (when (> (t2/count :model/User :is_active true, :type :personal) max-users) (throw (Exception. (trs "You have reached the maximum number of users ({0}) for your plan. Please upgrade to add more users." max-users)))))) |
(mu/defn- fetch-token-status* :- TokenStatus "Fetch info about the validity of `token` from the MetaStore." [token :- TokenStr] ;; NB that we fetch any settings from this thread, not inside on of the futures in the inner fetch calls. We ;; will have taken a lock to call through to here, and could create a deadlock with the future's thread. See ;; https://github.com/metabase/metabase/pull/38029/ (cond (mc/validate [:re RemoteCheckedToken] token) ;; attempt to query the metastore API about the status of this token. If the request doesn't complete in a ;; reasonable amount of time throw a timeout exception (let [site-uuid (setting/get :site-uuid-for-premium-features-token-checks)] (try (fetch-token-and-parse-body token token-check-url site-uuid) (catch Exception e1 (log/errorf e1 "Error fetching token status from %s:" 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 site-uuid) ;; 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/errorf e2 "Error fetching token status from %s:" store-url) (let [body (u/ignore-exceptions (some-> (ex-data e1) :body json/decode+kw))] (or body {:valid false :status (tru "Unable to validate token") :error-details (.getMessage e1)}))))))) (mc/validate [:re AirgapToken] token) (do (log/infof "Checking airgapped token '%s'..." (u.str/mask token)) (decode-airgap-token token)) :else (do (log/error (u/format-color 'red "Invalid token format!")) {:valid false :status "invalid" :error-details (trs "Token should be a valid 64 hexadecimal character token or an airgap token.")}))) | |
Locked version of | (let [lock (Object.)] (defn fetch-token-status [token] (locking lock (fetch-token-status* token)))) |
(declare token-valid-now?) | |
(mu/defn- valid-token->features :- [:set ms/NonBlankString] [token :- TokenStr] (assert ((requiring-resolve 'metabase.db/db-is-set-up?)) "Metabase DB is not yet set up") (let [{:keys [valid status features error-details] :as token-status} (fetch-token-status token)] ;; if token isn't valid throw an Exception with the `:status` message (when-not valid (throw (ex-info status {:status-code 400, :error-details error-details}))) (when (and (mc/validate [:re AirgapToken] token) (not (token-valid-now? token-status))) (throw (ex-info status {:status-code 400 :error-details (tru "Airgapped token is no longer valid. Please contact Metabase support.")}))) ;; otherwise return the features this token supports (set features))) | |
(defsetting token-status (deferred-tru "Cached token status for premium features. This is to avoid an API request on the the first page load.") :visibility :admin :type :json :audit :never :setter :none :getter (fn [] (some-> (premium-embedding-token) (fetch-token-status)))) | |
+----------------------------------------------------------------------------------------------------------------+ | SETTING & RELATED FNS | +----------------------------------------------------------------------------------------------------------------+ | |
(defsetting premium-embedding-token ; TODO - rename this to premium-features-token? (deferred-tru "Token for premium features. Go to the MetaStore to get yours!") :audit :never :sensitive? true :setter (fn [new-value] ;; validate the new value if we're not unsetting it (try (when (seq new-value) (when (mc/validate [:re AirgapToken] new-value) (airgap-check-user-count)) (when-not (or (mc/validate [:re RemoteCheckedToken] new-value) (mc/validate [:re AirgapToken] new-value)) (throw (ex-info (tru "Token format is invalid.") {:status-code 400, :error-details "Token should be 64 hexadecimal characters."}))) (valid-token->features new-value) (log/info "Token is valid.")) (setting/set-value-of-type! :string :premium-embedding-token new-value) (catch Throwable e (log/error e "Error setting premium features token") (throw (ex-info (.getMessage e) (merge {:message (.getMessage e), :status-code 400} (ex-data e)))))))) ; merge in error-details if present | |
Returns true if the current instance is airgapped. | (defsetting airgap-enabled :type :boolean :visibility :public :setter :none :audit :never :export? false :getter (fn [] (mc/validate AirgapToken (premium-embedding-token)))) |
(let [cached-logger (memoize/ttl ^{::memoize/args-fn (fn [[token _e]] [token])} (fn [_token e] (log/error "Error validating token:" (ex-message e)) (log/debug e "Error validating token")) ;; log every five minutes :ttl/threshold (* 1000 60 5))] (mu/defn ^:dynamic *token-features* :- [:set ms/NonBlankString] "Get the features associated with the system's premium features token." [] (try (or (some-> (premium-embedding-token) valid-token->features) #{}) (catch Throwable e (cached-logger (premium-embedding-token) e) #{})))) | |
(mu/defn plan-alias :- [:maybe :string] "Returns a string representing the instance's current plan, if included in the last token status request." [] (some-> (premium-embedding-token) fetch-token-status :plan-alias)) | |
True if we have a valid premium features token with ANY features. | (defn has-any-features? [] (boolean (seq (*token-features*)))) |
Does this instance's premium token have (has-feature? :sandboxes) ; -> true (has-feature? :toucan-management) ; -> false | (defn has-feature? [feature] (contains? (*token-features*) (name feature))) |
Returns an error that can be used to throw when an enterprise feature check fails. | (defn ee-feature-error [feature-name] (ex-info (tru "{0} is a paid feature not currently available to your instance. Please upgrade to use it. Learn more at metabase.com/upgrade/" feature-name) {:status-code 402 :status "error-premium-feature-not-available"})) |
Check if an token with | (mu/defn assert-has-feature [feature-flag :- keyword? feature-name :- [:or string? mu/localized-string-schema]] (when-not (has-feature? feature-flag) (throw (ee-feature-error feature-name)))) |
Check if has at least one of feature in | (mu/defn assert-has-any-features [feature-flag :- [:sequential keyword?] feature-name :- [:or string? mu/localized-string-schema]] (when-not (some has-feature? feature-flag) (throw (ee-feature-error feature-name)))) |
(defn- default-premium-feature-getter [feature] (fn [] (and config/ee-available? (has-feature? feature)))) | |
Set of defined premium feature keywords. | (def premium-features (atom #{})) |
Convenience for generating a [[metabase.models.setting/defsetting]] form for a premium token feature. (The Settings definitions for Premium token features all look more or less the same, so this prevents a lot of code duplication.) | (defmacro ^:private define-premium-feature [setting-name docstring feature & {:as options}] (let [options (merge {:type :boolean :visibility :public :setter :none :audit :never :getter `(default-premium-feature-getter ~(some-> feature name))} options)] `(do (swap! premium-features conj ~feature) (defsetting ~setting-name ~docstring ~@(mapcat identity options))))) |
Logo Removal and Full App Embedding. Should we hide the 'Powered by Metabase' attribution on the embedding pages?
| (define-premium-feature hide-embed-branding? :embedding :export? true ;; This specific feature DOES NOT require the EE code to be present in order for it to return truthy, unlike ;; everything else. :getter #(has-feature? :embedding)) |
Should we allow users embed the SDK in sites other than localhost? | (define-premium-feature enable-embedding-sdk-origins? :embedding-sdk) |
Should we allow full whitelabel embedding (reskinning the entire interface?) | (define-premium-feature enable-whitelabeling? :whitelabel :export? true) |
Should we enable the Audit Logs interface in the Admin UI? | (define-premium-feature enable-audit-app? :audit-app) |
Should we enable restrict email domains for subscription recipients? | (define-premium-feature ^{:added "0.41.0"} enable-email-allow-list? :email-allow-list) |
Should we enable granular controls for cache TTL at the database, dashboard, and card level? | (define-premium-feature ^{:added "0.41.0"} enable-cache-granular-controls? :cache-granular-controls) |
Should we enable preemptive caching; i.e., auto-refresh of cached results? | (define-premium-feature ^{:added "1.53.0"} enable-preemptive-caching? :cache-preemptive) |
Should we enable initialization on launch from a config file? | (define-premium-feature ^{:added "0.41.0"} enable-config-text-file? :config-text-file) |
Should we enable data sandboxes (row-level permissions)? | (define-premium-feature enable-sandboxes? :sandboxes :export? true) |
Should we enable JWT-based authentication? | (define-premium-feature enable-sso-jwt? :sso-jwt) |
Should we enable SAML-based authentication? | (define-premium-feature enable-sso-saml? :sso-saml) |
Should we enable advanced configuration for LDAP authentication? | (define-premium-feature enable-sso-ldap? :sso-ldap) |
Should we enable advanced configuration for Google Sign-In authentication? | (define-premium-feature enable-sso-google? :sso-google) |
Should we enable user/group provisioning via SCIM? | (define-premium-feature enable-scim? :scim) |
Should we enable any SSO-based authentication? | (defn enable-any-sso? [] (or (enable-sso-jwt?) (enable-sso-saml?) (enable-sso-ldap?) (enable-sso-google?))) |
Should we enable configuring session timeouts? | (define-premium-feature enable-session-timeout-config? :session-timeout-config) |
Can we disable login by password? | (define-premium-feature can-disable-password-login? :disable-password-login) |
Should we enable filters for dashboard subscriptions? | (define-premium-feature ^{:added "0.41.0"} enable-dashboard-subscription-filters? :dashboard-subscription-filters) |
Should we enable extra knobs around permissions (block access, and in the future, moderator roles, feature-level permissions, etc.)? | (define-premium-feature ^{:added "0.41.0"} enable-advanced-permissions? :advanced-permissions) |
Should we enable verified content, like verified questions and models (and more in the future, like actions)? | (define-premium-feature ^{:added "0.41.0"} enable-content-verification? :content-verification) |
Should we enable Official Collections? | (define-premium-feature ^{:added "0.41.0"} enable-official-collections? :official-collections) |
Should we enable SQL snippet folders? | (define-premium-feature ^{:added "0.41.0"} enable-snippet-collections? :snippet-collections) |
Enable the v2 SerDes functionality | (define-premium-feature ^{:added "0.45.0"} enable-serialization? :serialization) |
Enable restrict email recipients? | (define-premium-feature ^{:added "0.47.0"} enable-email-restrict-recipients? :email-restrict-recipients) |
Enable automatic descriptions of questions and dashboards by LLMs? | (define-premium-feature ^{:added "0.50.0"} enable-llm-autodescription? :llm-autodescription) |
Enable the Query Validator Tool? | (define-premium-feature ^{:added "0.51.0"} enable-query-reference-validation? :query-reference-validation) |
Should we allow admins to clean up tables created from uploads? | (define-premium-feature enable-upload-management? :upload-management) |
Does the Metabase Cloud instance have an internal data warehouse attached? | (define-premium-feature has-attached-dwh? :attached-dwh) |
Is the Metabase instance running in the cloud? | (defsetting is-hosted? :type :boolean :visibility :public :setter :none :audit :never :getter (fn [] (boolean (and ((*token-features*) "hosting") (not (airgap-enabled))))) :doc false) |
Returns true when we should record audit data into the audit log. | (defn log-enabled? [] (or (is-hosted?) (has-feature? :audit-app))) |
Should we various other enhancements, e.g. NativeQuerySnippet collection permissions?
By checking whether DEPRECATED -- it should now be possible to use the new 0.41.0+ features for everything previously covered by 'enhancements'. | (define-premium-feature ^:deprecated enable-enhancements? :enhancements :getter #(and config/ee-available? (has-any-features?))) |
Should we enable Collection Cleanup? | (define-premium-feature ^{:added "0.51.0"} enable-collection-cleanup? :collection-cleanup) |
Should we enable database auth-providers? | (define-premium-feature ^{:added "0.51.0"} enable-database-auth-providers? :database-auth-providers) |
+----------------------------------------------------------------------------------------------------------------+ | Defenterprise Macro | +----------------------------------------------------------------------------------------------------------------+ | |
Is the current namespace an Enterprise Edition namespace? | (defn- in-ee? [] (str/starts-with? (ns-name *ns*) "metabase-enterprise")) |
A map from fully-qualified EE function names to maps which include their EE and OSS implementations, as well as any additional options. This information is used to dynamically dispatch a call to the right implementation, depending on the available feature flags.
| (defonce registry (atom {})) |
Adds new values to the | (defn register-mapping! [ee-fn-name values] (swap! registry update ee-fn-name merge values)) |
(defn- check-feature [feature] (or (= feature :none) (has-feature? feature))) | |
Dynamically tries to require an enterprise namespace and determine the correct implementation to call, based on the availability of EE code and the necessary premium feature. Returns a fn which, when invoked, applies its args to one of the EE implementation, the OSS implementation, or the fallback function. | (defn dynamic-ee-oss-fn [ee-ns ee-fn-name] (let [try-require-ee-ns-once (delay (u/ignore-exceptions (classloader/require ee-ns)))] (fn [& args] @try-require-ee-ns-once (let [{:keys [ee oss feature fallback]} (get @registry ee-fn-name)] (cond (and ee (check-feature feature)) (apply ee args) (and ee (fn? fallback)) (apply fallback args) :else (apply oss args)))))) |
Throws an exception if the required :feature option is not present. | (defn- validate-ee-args [{feature :feature :as options}] (when-not feature (throw (ex-info (trs "The :feature option is required when using defenterprise in an EE namespace!") {:options options})))) |
The exception to throw when the provided option is not included in the | (defn- oss-options-error [option options] (ex-info (trs "{0} option for defenterprise should not be set in an OSS namespace! Set it on the EE function instead." option) {:options options})) |
Throws exceptions if EE options are provided, or if an EE namespace is not provided. | (defn validate-oss-args [ee-ns {:keys [feature fallback] :as options}] (when-not ee-ns (throw (Exception. (str (trs "An EE namespace must be provided when using defenterprise in an OSS namespace!") " " (trs "Add it immediately before the argument list."))))) (when feature (throw (oss-options-error :feature options))) (when fallback (throw (oss-options-error :fallback options)))) |
The exception to throw when defenterprise is used without a docstring. | (defn- docstr-exception [fn-name] (Exception. (tru "Enterprise function {0}/{1} does not have a docstring. Go add one!" (ns-name *ns*) fn-name))) |
Impl macro for | (defmacro defenterprise-impl [{:keys [fn-name docstr ee-ns fn-tail options schema? return-schema]}] (when-not docstr (throw (docstr-exception fn-name))) (let [oss-or-ee (if (in-ee?) :ee :oss)] (case oss-or-ee :ee (validate-ee-args options) :oss (validate-oss-args '~ee-ns options)) `(let [ee-ns# '~(or ee-ns (ns-name *ns*)) ee-fn-name# (symbol (str ee-ns# "/" '~fn-name)) oss-or-ee-fn# ~(if schema? `(mu/fn ~(symbol (str fn-name)) :- ~return-schema ~@fn-tail) `(fn ~(symbol (str fn-name)) ~@fn-tail))] (register-mapping! ee-fn-name# (merge ~options {~oss-or-ee oss-or-ee-fn#})) (def ~(vary-meta fn-name assoc :arglists ''([& args])) ~docstr (dynamic-ee-oss-fn ee-ns# ee-fn-name#))))) |
(defn- options-conformer [conformed-options] (into {} (map (comp (juxt :k :v) second) conformed-options))) | |
(s/def ::defenterprise-options (s/& (s/* (s/alt :feature (s/cat :k #{:feature} :v keyword?) :fallback (s/cat :k #{:fallback} :v #(or (#{:oss} %) (symbol? %))))) (s/conformer options-conformer))) | |
(s/def ::defenterprise-args (s/cat :docstr (s/? string?) :ee-ns (s/? symbol?) :options (s/? ::defenterprise-options) :fn-tail (s/* any?))) | |
(s/def ::defenterprise-schema-args (s/cat :return-schema (s/? (s/cat :- #{:-} :schema any?)) :defenterprise-args (s/? ::defenterprise-args))) | |
Defines a function that has separate implementations between the Metabase Community Edition (aka OSS) and Enterprise Edition (EE). When used in a OSS namespace, defines a function that should have a corresponding implementation in an EE namespace (using the same macro). The EE implementation will be used preferentially to the OSS implementation if it is available. The first argument after the function name should be a symbol of the namespace containing the EE implementation. The corresponding EE function must have the same name as the OSS function. When used in an EE namespace, the namespace of the corresponding OSS implementation does not need to be included -- it will be inferred automatically, as long as a corresponding [[defenterprise]] call exists in an OSS namespace. Two additional options can be defined, when using this macro in an EE namespace. These options should be defined immediately before the args list of the function: `:feature`A keyword representing a premium feature which must be present for the EE implementation to be used. Use `:fallback`The keyword | (defmacro defenterprise [fn-name & defenterprise-args] {:pre [(symbol? fn-name)]} (let [parsed-args (s/conform ::defenterprise-args defenterprise-args) _ (when (s/invalid? parsed-args) (throw (ex-info "Failed to parse defenterprise args" (s/explain-data ::defenterprise-args parsed-args)))) args (assoc parsed-args :fn-name fn-name)] `(defenterprise-impl ~args))) |
A version of defenterprise which allows for schemas to be defined for the args and return value. Schema syntax is
the same as when using | (defmacro defenterprise-schema [fn-name & defenterprise-args] {:pre [(symbol? fn-name)]} (let [parsed-args (s/conform ::defenterprise-schema-args defenterprise-args) _ (when (s/invalid? parsed-args) (throw (ex-info "Failed to parse defenterprise-schema args" (s/explain-data ::defenterprise-schema-args parsed-args)))) args (-> (:defenterprise-args parsed-args) (assoc :schema? true) (assoc :return-schema (-> parsed-args :return-schema :schema)) (assoc :fn-name fn-name))] `(defenterprise-impl ~args))) |
Returns a boolean if the current user uses sandboxing for any database. In OSS this is always false. Will throw an error if [[api/current-user-id]] is not bound. | (defenterprise sandboxed-user? metabase-enterprise.sandbox.api.util [] (when-not api/*current-user-id* ;; If no *current-user-id* is bound we can't check for sandboxes, so we should throw in this case to avoid ;; returning `false` for users who should actually be sandboxes. (throw (ex-info (str (tru "No current user found")) {:status-code 403}))) ;; oss doesn't have sandboxing. But we throw if no current-user-id so the behavior doesn't change when ee version ;; becomes available false) |
Returns a boolean if the current user uses connection impersonation for any database. In OSS this is always false. Will throw an error if [[api/current-user-id]] is not bound. | (defenterprise impersonated-user? metabase-enterprise.advanced-permissions.api.util [] (when-not api/*current-user-id* ;; If no *current-user-id* is bound we can't check for impersonations, so we should throw in this case to avoid ;; returning `false` for users who should actually be using impersonations. (throw (ex-info (str (tru "No current user found")) {:status-code 403}))) ;; oss doesn't have connection impersonation. But we throw if no current-user-id so the behavior doesn't change when ;; ee version becomes available false) |
Returns a boolean if the current user has an enforced connection impersonation policy for a provided database. In OSS this is always false. Will throw an error if [[api/current-user-id]] is not bound. | (defenterprise impersonation-enforced-for-db? metabase-enterprise.advanced-permissions.api.util [_db-or-id] (when-not api/*current-user-id* ;; If no *current-user-id* is bound we can't check for impersonations, so we should throw in this case to avoid ;; returning `false` for users who should actually be using impersonations. (throw (ex-info (str (tru "No current user found")) {:status-code 403}))) ;; oss doesn't have connection impersonation. But we throw if no current-user-id so the behavior doesn't change when ;; ee version becomes available false) |
Returns a boolean if the current user uses sandboxing or connection impersonation for any database. In OSS is always false. Will throw an error if [[api/current-user-id]] is not bound. | (defn sandboxed-or-impersonated-user? [] (or (sandboxed-user?) (impersonated-user?))) |
In OSS, this returns an empty map. In OSS, this returns false. | (defenterprise decode-airgap-token metabase-enterprise.airgap [_] {}) (defenterprise token-valid-now? metabase-enterprise.airgap [_] false) |