mirror of
https://github.com/penpot/penpot.git
synced 2026-11-12 13:00:37 +00:00
🐛 Enforce sso before creating organization teams (#12980)
This commit is contained in:
parent
34f0d12b99
commit
df0092fc60
@ -13,6 +13,7 @@
|
||||
[app.common.types.organization :as cto]
|
||||
[app.common.types.team :as ctt]
|
||||
[app.common.uri :as u]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.helpers :as dsh]
|
||||
@ -24,7 +25,9 @@
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.util.clipboard :as clipboard]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.session-state :as ss]
|
||||
[app.util.storage :as storage]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.string :as str]
|
||||
@ -111,8 +114,8 @@
|
||||
(f team)))))))
|
||||
|
||||
(defn check-and-create-team
|
||||
"Fetches fresh team data from the server to ensure up-to-date organization
|
||||
permissions, then shows the team-form modal or a no-permission modal."
|
||||
"Fetches fresh team data, checks organization permissions and SSO, then
|
||||
shows the team form or the appropriate access error."
|
||||
[team-id]
|
||||
(ptk/reify ::check-and-create-team
|
||||
ptk/WatchEvent
|
||||
@ -127,13 +130,44 @@
|
||||
{:organization-perms organization
|
||||
:profile-id profile-id
|
||||
:team-perms (:permissions team)})
|
||||
true)]
|
||||
(rx/of (if can-create?
|
||||
(modal/show :team-form (if in-organization?
|
||||
{:organization-id (:id organization)
|
||||
:organization-name (:name organization)}
|
||||
{}))
|
||||
(modal/show :no-permission-modal {:type :create-team}))))))))))
|
||||
true)
|
||||
team-form-modal (modal/show :team-form (if in-organization?
|
||||
{:organization-id (:id organization)
|
||||
:organization-name (:name organization)}
|
||||
{}))]
|
||||
(cond
|
||||
(not can-create?)
|
||||
(rx/of (modal/show :no-permission-modal {:type :create-team}))
|
||||
|
||||
(not in-organization?)
|
||||
(rx/of team-form-modal)
|
||||
|
||||
:else
|
||||
(let [pending-id (str (uuid/next))
|
||||
callback-url (dom/append-query-param (rt/get-current-href)
|
||||
:pending-action-id pending-id)]
|
||||
(->> (rp/cmd! :check-nitrate-sso {:team-id team-id
|
||||
:url callback-url})
|
||||
(rx/mapcat
|
||||
(fn [{:keys [authorized redirect-uri]}]
|
||||
(cond
|
||||
authorized
|
||||
(rx/of team-form-modal)
|
||||
|
||||
redirect-uri
|
||||
(do
|
||||
(ss/save-pending-action! pending-id
|
||||
{:type :create-team-in-organization
|
||||
:team-id team-id})
|
||||
(rx/of (rt/nav-raw :uri (str redirect-uri))))
|
||||
|
||||
:else
|
||||
(rx/of (rt/assign-exception
|
||||
{:type :sso-error
|
||||
:organization-id (:id organization)
|
||||
:organization-name (:name organization)
|
||||
:team-id team-id
|
||||
:is-dashboard true})))))))))))))))
|
||||
|
||||
(defn check-and-delete-team
|
||||
"Fetches fresh team data from the server to ensure up-to-date organization
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
[app.main.data.plugins :as dp]
|
||||
[app.main.data.profile :as dprof]
|
||||
[app.main.data.project :as dpj]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
@ -298,6 +299,8 @@
|
||||
(st/emit! (dnt/add-team-to-organization {:team-id (:team-id action)
|
||||
:organization-id (:organization-id action)
|
||||
:skip-audit? true}))
|
||||
:create-team-in-organization
|
||||
(st/emit! (dtm/check-and-create-team (:team-id action)))
|
||||
nil)))))
|
||||
|
||||
(mf/defc dashboard*
|
||||
|
||||
@ -5,9 +5,13 @@
|
||||
(ns frontend-tests.data.team-test
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.ui.dashboard.team :as dtui]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.session-state :as ss]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.async :as async]
|
||||
@ -27,6 +31,89 @@
|
||||
:on-next #(swap! emitted conj %))))
|
||||
(.then (fn [_] {:team-id team-id :events @emitted})))))
|
||||
|
||||
(t/deftest ^:async check-and-create-team-redirects-to-sso-and-remembers-the-action
|
||||
(let [team-id "00000000-0000-4000-8000-000000000001"
|
||||
organization-id "00000000-0000-4000-8000-000000000002"
|
||||
profile-id "00000000-0000-4000-8000-000000000003"
|
||||
dashboard-url "https://penpot.example/#/dashboard-recent?team-id=current-team"
|
||||
callback-url (str dashboard-url "&pending-action-id=pending-id")
|
||||
team {:id team-id
|
||||
:organization {:id organization-id
|
||||
:name "Product"
|
||||
:owner-id profile-id}}
|
||||
requests (atom [])
|
||||
saved-actions (atom [])
|
||||
emitted (atom [])]
|
||||
(await
|
||||
(mock/with-mocks*
|
||||
{rp/cmd! (mock/stub
|
||||
(fn [command params]
|
||||
(swap! requests conj [command params])
|
||||
(case command
|
||||
:get-teams (rx/of [team])
|
||||
:check-nitrate-sso
|
||||
(rx/of {:authorized false
|
||||
:redirect-uri "https://sso.example.test/login"}))))
|
||||
rt/get-current-href (mock/stub (constantly dashboard-url))
|
||||
dom/append-query-param (mock/stub (fn [_url _key _value] callback-url))
|
||||
uuid/next (mock/stub (constantly "pending-id"))
|
||||
ss/save-pending-action! (mock/stub (fn [& args]
|
||||
(swap! saved-actions conj args)))
|
||||
rt/nav-raw (fn [& args] {:nav-raw args})
|
||||
modal/show (mock/stub (fn [& args] {:modal-show args}))}
|
||||
(await (async/observe
|
||||
(ptk/watch (dtm/check-and-create-team team-id)
|
||||
{:profile {:id profile-id}}
|
||||
nil)
|
||||
:on-next #(swap! emitted conj %)))))
|
||||
|
||||
(t/is (= [[:get-teams nil]
|
||||
[:check-nitrate-sso {:team-id team-id :url callback-url}]]
|
||||
@requests))
|
||||
(t/is (= [["pending-id" {:type :create-team-in-organization
|
||||
:team-id team-id}]]
|
||||
@saved-actions))
|
||||
(t/is (= {:nav-raw [:uri "https://sso.example.test/login"]}
|
||||
(last @emitted)))
|
||||
(t/is (not-any? #(contains? % :modal-show) @emitted))))
|
||||
|
||||
(t/deftest ^:async check-and-create-team-shows-form-after-sso-is-authorized
|
||||
(let [team-id "00000000-0000-4000-8000-000000000001"
|
||||
organization-id "00000000-0000-4000-8000-000000000002"
|
||||
profile-id "00000000-0000-4000-8000-000000000003"
|
||||
dashboard-url "https://penpot.example/#/dashboard-recent?team-id=current-team"
|
||||
callback-url (str dashboard-url "&pending-action-id=pending-id")
|
||||
team {:id team-id
|
||||
:organization {:id organization-id
|
||||
:name "Product"
|
||||
:owner-id profile-id}}
|
||||
requests (atom [])
|
||||
emitted (atom [])]
|
||||
(await
|
||||
(mock/with-mocks*
|
||||
{rp/cmd! (mock/stub
|
||||
(fn [command params]
|
||||
(swap! requests conj [command params])
|
||||
(case command
|
||||
:get-teams (rx/of [team])
|
||||
:check-nitrate-sso (rx/of {:authorized true}))))
|
||||
rt/get-current-href (mock/stub (constantly dashboard-url))
|
||||
dom/append-query-param (mock/stub (fn [_url _key _value] callback-url))
|
||||
uuid/next (mock/stub (constantly "pending-id"))
|
||||
modal/show (mock/stub (fn [& args] {:modal-show args}))}
|
||||
(await (async/observe
|
||||
(ptk/watch (dtm/check-and-create-team team-id)
|
||||
{:profile {:id profile-id}}
|
||||
nil)
|
||||
:on-next #(swap! emitted conj %)))))
|
||||
|
||||
(t/is (= [:check-nitrate-sso {:team-id team-id :url callback-url}]
|
||||
(last @requests)))
|
||||
(t/is (= {:modal-show [:team-form {:organization-id organization-id
|
||||
:organization-name "Product"}]}
|
||||
(last @emitted)))
|
||||
(t/is (= 2 (count @emitted)))))
|
||||
|
||||
(t/deftest ^:async fetch-invitations-clears-the-list-on-permission-error
|
||||
;; The pending invitations are an admin only list: a member that cannot
|
||||
;; invite gets an insufficient permissions error. That must not become an
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user