diff --git a/frontend/src/app/main/data/common.cljs b/frontend/src/app/main/data/common.cljs index d636d82601..949a5a84ec 100644 --- a/frontend/src/app/main/data/common.cljs +++ b/frontend/src/app/main/data/common.cljs @@ -11,7 +11,9 @@ [app.common.data.macros :as dm] [app.common.schema :as sm] [app.common.time :as ct] + [app.common.types.organization :as co] [app.common.types.team :as ctt] + [app.config :as cf] [app.main.data.helpers :as dsh] [app.main.data.modal :as modal] [app.main.data.notifications :as ntf] @@ -232,6 +234,63 @@ ;; Delay so the navigation can finish (rx/delay 250)))))))) +(defn- check-team-sso + [team-id] + (let [url (rt/get-current-href)] + (->> (rp/cmd! :check-nitrate-sso {:team-id team-id :url url}) + (rx/mapcat (fn [{:keys [authorized redirect-uri]}] + (if authorized + (rx/empty) + (if redirect-uri + (rx/of (rt/nav-raw :uri (str redirect-uri))) + (rx/empty)))))))) + +(defn handle-change-team-org + "Handle :team-org-change websocket messages on dashboard and workspace. + Updates local team org data and redirects to SSO when required." + [{:keys [team notification]}] + (ptk/reify ::handle-change-team-org + ptk/WatchEvent + (watch [_ state _] + (let [current-team-id (:current-team-id state) + organization (:organization team) + current-team? (= (:id team) current-team-id)] + (when (and (contains? cf/flags :nitrate) + current-team?) + (rx/concat + (when notification + (rx/of (ntf/show {:content (tr notification (:name organization)) + :type :toast + :level :info + :timeout nil}))) + (when (:id organization) + (check-team-sso current-team-id)))))) + ptk/UpdateEvent + (update [_ state] + (if (contains? cf/flags :nitrate) + (let [team-id (:id team) + team-name (:name team) + organization (:organization team)] + (d/update-in-when state [:teams team-id] + (fn [team] + (cond-> (co/apply-organization team organization) + team-name (assoc :name team-name))))) + state)))) + +(defn handle-organization-change-sso + "Handle :organization-change-sso websocket messages on dashboard and workspace. + Redirects to the org SSO login when the current team belongs to that org." + [{:keys [organization-id]}] + (ptk/reify ::handle-organization-change-sso + ptk/WatchEvent + (watch [_ state _] + (when (contains? cf/flags :nitrate) + (let [team-id (:current-team-id state) + team (dm/get-in state [:teams team-id]) + org-id (dm/get-in team [:organization :id])] + (when (= organization-id org-id) + (check-team-sso team-id))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PROGRESS EVENTS diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 10fa0aafdd..a573033183 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -13,7 +13,6 @@ [app.common.logging :as log] [app.common.schema :as sm] [app.common.time :as ct] - [app.common.types.organization :as co] [app.common.types.project :refer [valid-project?]] [app.common.uuid :as uuid] [app.config :as cf] @@ -27,7 +26,6 @@ [app.main.data.team :as dtm] [app.main.data.websocket :as dws] [app.main.repo :as rp] - [app.main.router :as rt] [app.main.store :as st] [app.util.i18n :as i18n :refer [tr]] [app.util.sse :as sse] @@ -687,47 +685,6 @@ (rx/of (dcm/change-team-role params) (modal/hide))))) -(defn- check-team-sso - [team-id] - (let [url (rt/get-current-href)] - (->> (rp/cmd! :check-nitrate-sso {:team-id team-id :url url}) - (rx/mapcat (fn [{:keys [authorized redirect-uri]}] - (if authorized - (rx/empty) - (if redirect-uri - (rx/of (rt/nav-raw :uri (str redirect-uri))) - (rx/empty)))))))) - -(defn handle-change-team-org - [{:keys [team notification]}] - (ptk/reify ::handle-change-team-org - ptk/WatchEvent - (watch [_ state _] - (let [current-team-id (:current-team-id state) - organization (:organization team) - current-team? (= (:id team) current-team-id)] - (when (and (contains? cf/flags :nitrate) - current-team?) - (rx/concat - (when notification - (rx/of (ntf/show {:content (tr notification (:name organization)) - :type :toast - :level :info - :timeout nil}))) - (when (:id organization) - (check-team-sso current-team-id)))))) - ptk/UpdateEvent - (update [_ state] - (if (contains? cf/flags :nitrate) - (let [team-id (:id team) - team-name (:name team) - organization (:organization team)] - (d/update-in-when state [:teams team-id] - (fn [team] - (cond-> (co/apply-organization team organization) - team-name (assoc :name team-name))))) - state)))) - (defn- handle-user-org-change [{:keys [organization-id organization-name notification]}] (ptk/reify ::handle-user-org-change @@ -772,28 +729,16 @@ (when fetch? ;; If the user belonged to the org (rx/of (dtm/fetch-teams))))))))) -(defn- handle-nitrate-change-sso - [{:keys [organization-id]}] - (ptk/reify ::handle-nitrate-change-sso - ptk/WatchEvent - (watch [_ state _] - (when (contains? cf/flags :nitrate) - (let [team-id (:current-team-id state) - team (dm/get-in state [:teams team-id]) - org-id (dm/get-in team [:organization :id])] - (when (= organization-id org-id) - (check-team-sso team-id))))))) - (defn- process-message [{:keys [type] :as msg}] (case type :notification (dcm/handle-notification msg) :team-role-change (handle-change-team-role msg) :team-membership-change (dcm/team-membership-change msg) - :team-org-change (handle-change-team-org msg) + :team-org-change (dcm/handle-change-team-org msg) :user-org-change (handle-user-org-change msg) :organization-deleted (handle-organization-deleted msg) - :organization-change-sso (handle-nitrate-change-sso msg) + :organization-change-sso (dcm/handle-organization-change-sso msg) nil)) diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index c9b054d881..440b2496e3 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -125,18 +125,20 @@ (defn- process-message [{:keys [type] :as msg}] (case type - :join-file (handle-presence msg) - :leave-file (handle-presence msg) - :presence (handle-presence msg) - :disconnect (handle-presence msg) - :pointer-update (handle-pointer-update msg) - :file-change (handle-file-change msg) - :file-deleted (handle-file-deleted msg) - :file-restored (handle-file-restored msg) - :library-change (handle-library-change msg) - :notification (dc/handle-notification msg) - :team-role-change (handle-change-team-role msg) - :team-membership-change (dc/team-membership-change msg) + :join-file (handle-presence msg) + :leave-file (handle-presence msg) + :presence (handle-presence msg) + :disconnect (handle-presence msg) + :pointer-update (handle-pointer-update msg) + :file-change (handle-file-change msg) + :file-deleted (handle-file-deleted msg) + :file-restored (handle-file-restored msg) + :library-change (handle-library-change msg) + :notification (dc/handle-notification msg) + :team-role-change (handle-change-team-role msg) + :team-membership-change (dc/team-membership-change msg) + :team-org-change (dc/handle-change-team-org msg) + :organization-change-sso (dc/handle-organization-change-sso msg) nil)) (defn- handle-pointer-send diff --git a/frontend/test/frontend_tests/data/dashboard_test.cljs b/frontend/test/frontend_tests/data/dashboard_test.cljs index 475a5891f9..d56b034a6d 100644 --- a/frontend/test/frontend_tests/data/dashboard_test.cljs +++ b/frontend/test/frontend_tests/data/dashboard_test.cljs @@ -8,7 +8,7 @@ (:require [app.common.uuid :as uuid] [app.config :as cf] - [app.main.data.dashboard :as dd] + [app.main.data.common :as dcm] [app.main.repo :as rp] [app.main.router :as rt] [beicon.v2.core :as rx] @@ -23,7 +23,7 @@ current-url (str "https://penpot.example.com/#/dashboard/recent?team-id=" team-id) redirect-url "https://idp.example.com/authorize" state {:current-team-id team-id} - event (dd/handle-change-team-org + event (dcm/handle-change-team-org {:team {:id team-id :organization organization} :notification nil})] (mock/with-mocks @@ -48,3 +48,37 @@ (fn [] (done'))))) done)))) + +(t/deftest organization-sso-activation-redirects-current-team + (t/async done + (let [team-id (uuid/next) + org-id (uuid/next) + current-url (str "https://penpot.example.com/#/workspace?team-id=" team-id) + redirect-url "https://idp.example.com/authorize" + state {:current-team-id team-id + :teams {team-id {:id team-id + :organization {:id org-id}}}} + event (dcm/handle-organization-change-sso + {:organization-id org-id})] + (mock/with-mocks + {cf/flags (conj cf/flags :nitrate) + rp/cmd! (mock/stub + (fn [cmd params] + (if (= [:check-nitrate-sso + {:team-id team-id :url current-url}] + [cmd params]) + (rx/of {:authorized false :redirect-uri redirect-url}) + (rx/throw (ex-info "unexpected RPC" {:cmd cmd :params params}))))) + rt/get-current-href (mock/stub (constantly current-url))} + (fn [done'] + (->> (ptk/watch event state nil) + (rx/reduce conj []) + (rx/subs! + (fn [events] + (t/is (= [::rt/nav-raw] (mapv ptk/type events)))) + (fn [error] + (t/is false (str "unexpected error: " error)) + (done')) + (fn [] + (done'))))) + done))))