diff --git a/backend/resources/app/templates/debug.tmpl b/backend/resources/app/templates/debug.tmpl index 1f803140a2..42894b570c 100644 --- a/backend/resources/app/templates/debug.tmpl +++ b/backend/resources/app/templates/debug.tmpl @@ -12,43 +12,22 @@ Debug Main Page
-
- Error reports - CLICK HERE TO SEE THE ERROR REPORTS -
- Profile Management -
-
- -
- -
- - -
- - This is a just a security double check for prevent non intentional submits. - -
- -
- - -
- -
- - -
-
+ CURRENT PROFILE + +

+ Name: {{profile.fullname}}
+ Email: {{profile.email}} +

+
VIRTUAL CLOCK +

IMPORTANT: The virtual clock is profile based and only affects the currently logged-in profile.

CURRENT CLOCK: {{current-clock}}
@@ -81,8 +60,93 @@ Debug Main Page

+
+ ERROR REPORTS + CLICK HERE TO SEE THE ERROR REPORTS +
+ +
+
+ Profile Management +
+
+ +
+ +
+ + +
+ + This is a just a security double check for prevent non intentional submits. + +
+ +
+ + +
+ +
+ + +
+
+
+ + +
+ Feature Flags for Team + Add a feature flag to a team +
+
+ +
+
+ +
+ +
+ +
+ +
+ + +
+ + Do not check if the feature is supported + +
+ +
+ + +
+ + This is a just a security double check for prevent non intentional submits. + +
+ +
+ +
+
+
+
+ +
@@ -173,55 +237,5 @@ Debug Main Page
- -
-
- Feature Flags for Team - Add a feature flag to a team -
-
- -
-
- -
- -
- -
- -
- - -
- - Do not check if the feature is supported - -
- -
- - -
- - This is a just a security double check for prevent non intentional submits. - -
- -
- -
-
-
-
{% endblock %} diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index 6df4398116..f744b00eb1 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -49,13 +49,16 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn index-handler - [_cfg _request] - (let [{:keys [clock offset]} @clock/current] + [cfg request] + (let [profile-id (::session/profile-id request) + offset (clock/get-offset profile-id) + profile (profile/get-profile cfg profile-id)] {::yres/status 200 ::yres/headers {"content-type" "text/html"} ::yres/body (-> (io/resource "app/templates/debug.tmpl") (tmpl/render {:version (:full cf/version) - :current-clock (str clock) + :profile profile + :current-clock ct/*clock* :current-offset (if offset (ct/format-duration offset) "NO OFFSET") @@ -447,15 +450,16 @@ (defn- set-virtual-clock [_ {:keys [params] :as request}] - (let [offset (some-> params :offset str/trim not-empty ct/duration) - reset? (contains? params :reset)] + (let [offset (some-> params :offset str/trim not-empty ct/duration) + profile-id (::session/profile-id request) + reset? (contains? params :reset)] (if (= "production" (cf/get :tenant)) {::yres/status 501 ::yres/body "OPERATION NOT ALLOWED"} (do (if (or reset? (zero? (inst-ms offset))) - (clock/set-offset! nil) - (clock/set-offset! offset)) + (clock/assign-offset profile-id nil) + (clock/assign-offset profile-id offset)) {::yres/status 302 ::yres/headers {"location" "/dbg"}})))) @@ -495,7 +499,7 @@ (defn authorized? [pool {:keys [::session/profile-id]}] - (or (= "devenv" (cf/get :host)) + (or (and (= "devenv" (cf/get :host)) profile-id) (let [profile (ex/ignoring (profile/get-profile pool profile-id)) admins (or (cf/get :admins) #{})] (contains? admins (:email profile))))) diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 95548a6e3c..f9154ab135 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -20,6 +20,7 @@ [app.http.session.tasks :as-alias tasks] [app.main :as-alias main] [app.setup :as-alias setup] + [app.setup.clock :as clock] [app.tokens :as tokens] [integrant.core :as ig] [yetti.request :as yreq] @@ -229,18 +230,22 @@ (let [{:keys [type token claims metadata]} (get request ::http/auth-data)] (cond (= type :cookie) - (let [session (case (:ver metadata) - ;; BACKWARD COMPATIBILITY WITH OLD TOKENS - 0 (read-session manager token) - 1 (some->> (:sid claims) (read-session manager)) - nil) + (let [session + (case (:ver metadata) + ;; BACKWARD COMPATIBILITY WITH OLD TOKENS + 0 (read-session manager token) + 1 (some->> (:sid claims) (read-session manager)) + nil) - request (cond-> request - (some? session) - (-> (assoc ::profile-id (:profile-id session)) - (assoc ::session session))) + request + (cond-> request + (some? session) + (-> (assoc ::profile-id (:profile-id session)) + (assoc ::session session))) - response (handler request)] + response + (binding [ct/*clock* (clock/get-clock (:profile-id session))] + (handler request))] (if (and session (renew-session? session)) (let [session (->> session diff --git a/backend/src/app/setup/clock.clj b/backend/src/app/setup/clock.clj index f73f6f8501..dac6370e27 100644 --- a/backend/src/app/setup/clock.clj +++ b/backend/src/app/setup/clock.clj @@ -9,48 +9,35 @@ modification of time offset (useful for testing and time adjustments)." (:require [app.common.logging :as l] - [app.common.time :as ct] - [app.setup :as-alias setup] - [integrant.core :as ig]) - (:import - java.time.Clock - java.time.Duration - java.time.Instant - java.time.ZoneId)) + [app.common.time :as ct])) -(defonce current - (atom {:clock (Clock/systemDefaultZone) - :offset nil})) +(defonce state + (atom {})) -(defmethod ig/init-key ::setup/clock - [_ _] - (add-watch current ::common - (fn [_ _ _ {:keys [clock offset]}] - (let [clock (if (ct/duration? offset) - (Clock/offset ^Clock clock - ^Duration offset) - clock)] - (l/wrn :hint "altering clock" :clock (str clock)) - (alter-var-root #'ct/*clock* (constantly clock)))))) +(defn assign-offset + "Assign virtual clock offset to a specific user. Is the responsability + of RPC module to properly bind the correct clock to the user + request." + [profile-id duration] + (swap! state (fn [state] + (if (nil? duration) + (dissoc state profile-id) + (assoc state profile-id duration))))) +(defn get-offset + [profile-id] + (get @state profile-id)) -(defmethod ig/halt-key! ::setup/clock - [_ _] - (remove-watch current ::common)) +(defn get-clock + [profile-id] + (if-let [offset (get-offset profile-id)] + (ct/offset-clock offset) + (ct/get-system-clock))) -(defn fixed - "Get fixed clock, mainly used in tests" - [instant] - (Clock/fixed ^Instant (ct/inst instant) - ^ZoneId (ZoneId/of "Z"))) - -(defn set-offset! - [duration] - (swap! current assoc :offset (some-> duration ct/duration))) - -(defn set-clock! +(defn set-global-clock ([] - (swap! current assoc :clock (Clock/systemDefaultZone))) + (set-global-clock (ct/get-system-clock))) ([clock] - (when (instance? Clock clock) - (swap! current assoc :clock clock)))) + (assert (ct/clock? clock) "expected valid clock instance") + (l/wrn :hint "altering clock" :clock (str clock)) + (alter-var-root #'ct/*clock* (constantly clock)))) diff --git a/backend/test/backend_tests/rpc_file_snapshot_test.clj b/backend/test/backend_tests/rpc_file_snapshot_test.clj index faedb6bb5e..509dc00bc7 100644 --- a/backend/test/backend_tests/rpc_file_snapshot_test.clj +++ b/backend/test/backend_tests/rpc_file_snapshot_test.clj @@ -17,7 +17,6 @@ [app.db.sql :as sql] [app.http :as http] [app.rpc :as-alias rpc] - [app.setup.clock :as clock] [app.storage :as sto] [backend-tests.helpers :as th] [clojure.test :as t] @@ -134,7 +133,7 @@ ;; this will run pending task triggered by deleting user snapshot (th/run-pending-tasks!) - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [res (th/run-task! :objects-gc {})] ;; delete 2 snapshots and 2 file data entries (t/is (= 4 (:processed res))))))))) diff --git a/backend/test/backend_tests/rpc_file_test.clj b/backend/test/backend_tests/rpc_file_test.clj index b676d87156..f327571448 100644 --- a/backend/test/backend_tests/rpc_file_test.clj +++ b/backend/test/backend_tests/rpc_file_test.clj @@ -19,7 +19,6 @@ [app.http :as http] [app.rpc :as-alias rpc] [app.rpc.commands.files :as files] - [app.setup.clock :as clock] [app.storage :as sto] [backend-tests.helpers :as th] [clojure.test :as t] @@ -922,7 +921,7 @@ (t/is (= 0 (:processed result)))) ;; run permanent deletion - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [result (th/run-task! :objects-gc {})] (t/is (= 3 (:processed result))))) @@ -1875,7 +1874,7 @@ file-id (uuid/next) now (ct/inst "2025-10-31T00:00:00Z")] - (binding [ct/*clock* (clock/fixed now)] + (binding [ct/*clock* (ct/fixed-clock now)] (let [data {::th/type :create-file ::rpc/profile-id (:id prof) :project-id proj-id @@ -1937,7 +1936,7 @@ file-id (uuid/next) now (ct/inst "2025-10-31T00:00:00Z")] - (binding [ct/*clock* (clock/fixed now)] + (binding [ct/*clock* (ct/fixed-clock now)] (let [data {::th/type :create-file ::rpc/profile-id (:id prof) :project-id proj-id @@ -2000,7 +1999,7 @@ team-id (:default-team-id profile) now (ct/inst "2025-10-31T00:00:00Z")] - (binding [ct/*clock* (clock/fixed now)] + (binding [ct/*clock* (ct/fixed-clock now)] (let [project (th/create-project* 1 {:profile-id (:id profile) :team-id team-id}) file (th/create-file* 1 {:profile-id (:id profile) diff --git a/backend/test/backend_tests/rpc_file_thumbnails_test.clj b/backend/test/backend_tests/rpc_file_thumbnails_test.clj index 31e6ac5402..9a856f3210 100644 --- a/backend/test/backend_tests/rpc_file_thumbnails_test.clj +++ b/backend/test/backend_tests/rpc_file_thumbnails_test.clj @@ -85,7 +85,7 @@ (t/is (map? (:result out)))) ;; run the task again - (let [res (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 31}))] + (let [res (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 31}))] (th/run-task! "storage-gc-touched" {}))] (t/is (= 2 (:freeze res)))) @@ -136,7 +136,7 @@ (t/is (some? (sto/get-object storage (:media-id row2)))) ;; run the task again - (let [res (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 31}))] + (let [res (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 31}))] (th/run-task! :storage-gc-touched {}))] (t/is (= 1 (:delete res))) (t/is (= 0 (:freeze res)))) @@ -147,7 +147,7 @@ ;; Run the storage gc deleted task, it should permanently delete ;; all storage objects related to the deleted thumbnails - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [res (th/run-task! :storage-gc-deleted {})] (t/is (= 1 (:deleted res))))) @@ -247,7 +247,7 @@ ;; Run the storage gc deleted task, it should permanently delete ;; all storage objects related to the deleted thumbnails - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [result (th/run-task! :storage-gc-deleted {})] (t/is (= 1 (:deleted result))))) diff --git a/backend/test/backend_tests/rpc_font_test.clj b/backend/test/backend_tests/rpc_font_test.clj index ef19218314..dbdf003494 100644 --- a/backend/test/backend_tests/rpc_font_test.clj +++ b/backend/test/backend_tests/rpc_font_test.clj @@ -12,7 +12,6 @@ [app.db :as db] [app.http :as http] [app.rpc :as-alias rpc] - [app.setup.clock :as clock] [app.storage :as sto] [backend-tests.helpers :as th] [clojure.test :as t] @@ -147,7 +146,7 @@ (t/is (= 0 (:freeze res))) (t/is (= 0 (:delete res)))) - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [res (th/run-task! :objects-gc {})] (t/is (= 2 (:processed res)))) @@ -208,7 +207,7 @@ (t/is (= 0 (:freeze res))) (t/is (= 0 (:delete res)))) - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [res (th/run-task! :objects-gc {})] (t/is (= 1 (:processed res)))) @@ -268,7 +267,7 @@ (t/is (= 0 (:freeze res))) (t/is (= 0 (:delete res)))) - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [res (th/run-task! :objects-gc {})] (t/is (= 1 (:processed res)))) diff --git a/backend/test/backend_tests/rpc_project_test.clj b/backend/test/backend_tests/rpc_project_test.clj index 6c75543f05..e2ebdaa034 100644 --- a/backend/test/backend_tests/rpc_project_test.clj +++ b/backend/test/backend_tests/rpc_project_test.clj @@ -12,7 +12,6 @@ [app.db :as db] [app.http :as http] [app.rpc :as-alias rpc] - [app.setup.clock :as clock] [backend-tests.helpers :as th] [clojure.test :as t])) @@ -228,7 +227,7 @@ (t/is (= 0 (count result))))) ;; run permanent deletion - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [result (th/run-task! :objects-gc {})] (t/is (= 1 (:processed result))))) diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj index 12791e761a..daf09e72a7 100644 --- a/backend/test/backend_tests/rpc_team_test.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -13,7 +13,6 @@ [app.db :as db] [app.http :as http] [app.rpc :as-alias rpc] - [app.setup.clock :as clock] [app.storage :as sto] [app.tokens :as tokens] [backend-tests.helpers :as th] @@ -526,7 +525,7 @@ (t/is (= :not-found (:type edata))))) ;; run permanent deletion - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [result (th/run-task! :objects-gc {})] (t/is (= 2 (:processed result))))) @@ -583,7 +582,7 @@ (t/is (= 1 (count rows))) (t/is (ct/inst? (:deleted-at (first rows))))) - (binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))] (let [result (th/run-task! :objects-gc {})] (t/is (= 7 (:processed result))))))) diff --git a/backend/test/backend_tests/storage_test.clj b/backend/test/backend_tests/storage_test.clj index a387074c4c..cd058af250 100644 --- a/backend/test/backend_tests/storage_test.clj +++ b/backend/test/backend_tests/storage_test.clj @@ -11,7 +11,6 @@ [app.common.uuid :as uuid] [app.db :as db] [app.rpc :as-alias rpc] - [app.setup.clock :as clock] [app.storage :as sto] [backend-tests.helpers :as th] [clojure.test :as t] @@ -99,14 +98,14 @@ ::sto/expired-at (ct/in-future {:hours 1}) :content-type "text/plain"})] - (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 0}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 0}))] (let [res (th/run-task! :storage-gc-deleted {})] (t/is (= 1 (:deleted res))))) (let [res (th/db-exec-one! ["select count(*) from storage_object;"])] (t/is (= 2 (:count res)))) - (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 61}))] + (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 61}))] (let [res (th/run-task! :storage-gc-deleted {})] (t/is (= 1 (:deleted res))))) @@ -331,22 +330,22 @@ :content-type "text/plain"})] - (binding [ct/*clock* (clock/fixed now)] + (binding [ct/*clock* (ct/fixed-clock now)] (let [res (th/run-task! :storage-gc-touched {})] (t/is (= 0 (:freeze res))) (t/is (= 0 (:delete res))))) - (binding [ct/*clock* (clock/fixed (ct/plus now {:minutes 1}))] + (binding [ct/*clock* (ct/fixed-clock (ct/plus now {:minutes 1}))] (let [res (th/run-task! :storage-gc-touched {})] (t/is (= 0 (:freeze res))) (t/is (= 1 (:delete res))))) - (binding [ct/*clock* (clock/fixed (ct/plus now {:hours 1}))] + (binding [ct/*clock* (ct/fixed-clock (ct/plus now {:hours 1}))] (let [res (th/run-task! :storage-gc-deleted {})] (t/is (= 0 (:deleted res))))) - (binding [ct/*clock* (clock/fixed (ct/plus now {:hours 2}))] + (binding [ct/*clock* (ct/fixed-clock (ct/plus now {:hours 2}))] (let [res (th/run-task! :storage-gc-deleted {})] (t/is (= 0 (:deleted res))))))) diff --git a/common/src/app/common/time.cljc b/common/src/app/common/time.cljc index b0e2b6fe84..3aca6d447b 100644 --- a/common/src/app/common/time.cljc +++ b/common/src/app/common/time.cljc @@ -64,8 +64,33 @@ java.time.temporal.TemporalAmount java.time.temporal.TemporalUnit))) +(declare inst) + #?(:clj (def ^:dynamic *clock* (Clock/systemDefaultZone))) +#?(:clj + (defn clock? + [o] + (instance? Clock o))) + +#?(:clj + (defn get-system-clock + [] + (Clock/systemDefaultZone))) + +#?(:clj + (defn offset-clock + [offset] + (Clock/offset ^Clock (Clock/systemDefaultZone) ^Duration offset))) + +#?(:clj + (defn fixed-clock + [instant] + (Clock/fixed ^Instant (inst instant) + ^ZoneId (ZoneId/of "Z")))) + + + (defn now [] #?(:clj (Instant/now *clock*)