mirror of
https://github.com/penpot/penpot.git
synced 2026-07-29 17:36:17 +00:00
🐛 Fix SSO review
This commit is contained in:
parent
4cd34b22e2
commit
056cd3d379
@ -219,9 +219,15 @@
|
||||
<div
|
||||
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
||||
"{{ organization.name|abbreviate:50 }}" has set up single sign-on (SSO) in Penpot. Access to its
|
||||
teams and files goes
|
||||
through your organization's identity provider. If you can't get in, your account probably isn't
|
||||
in the directory yet.
|
||||
teams and files now goes through your organization's identity provider.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div
|
||||
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
||||
If you can't get in, your account probably isn't in the directory yet.
|
||||
To get access, contact the organization owner.
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@ -3,8 +3,10 @@ Hello!
|
||||
{{invited-by|abbreviate:25}} has invited you to join the organization “{{ organization.name|abbreviate:50 }}”.
|
||||
|
||||
{% if organization.sso-active %}
|
||||
"{{ organization.name|abbreviate:50 }}" has set up single sign-on (SSO) in Penpot. Access to its teams and files goes
|
||||
through your organization's identity provider. If you can't get in, your account probably isn't in the directory yet. To get access, contact the organization owner.
|
||||
"{{ organization.name|abbreviate:50 }}" has set up single sign-on (SSO) in Penpot. Access to its teams and files now goes
|
||||
through your organization's identity provider.
|
||||
|
||||
If you can't get in, your account probably isn't in the directory yet. To get access, contact the organization owner.
|
||||
{% endif %}
|
||||
|
||||
Accept invitation using this link:
|
||||
|
||||
@ -197,10 +197,15 @@
|
||||
<div
|
||||
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
||||
"{{ organization.name|abbreviate:50 }}" has set up single sign-on (SSO) in Penpot. Access to
|
||||
its
|
||||
teams and files goes
|
||||
through your organization's identity provider. If you can't get in, your account probably isn't
|
||||
in the directory yet.
|
||||
its teams and files now goes through your organization's identity provider.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div
|
||||
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
||||
If you can't get in, your account probably isn't in the directory yet.
|
||||
To get access, contact the organization owner.
|
||||
</div>
|
||||
</td>
|
||||
@ -257,4 +262,4 @@
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@ -3,8 +3,10 @@ Hello!
|
||||
{{invited-by|abbreviate:25}} has invited you to join the team "{{ team|abbreviate:50 }}"{% if organization %}, part of the organization "{{ organization.name|abbreviate:50 }}"{% endif %}.
|
||||
|
||||
{% if organization.sso-active %}
|
||||
"{{ organization.name|abbreviate:50 }}" has set up single sign-on (SSO) in Penpot. Access to its teams and files goes
|
||||
through your organization's identity provider. If you can't get in, your account probably isn't in the directory yet. To get access, contact the organization owner.
|
||||
"{{ organization.name|abbreviate:50 }}" has set up single sign-on (SSO) in Penpot. Access to its teams and files now goes
|
||||
through your organization's identity provider.
|
||||
|
||||
If you can't get in, your account probably isn't in the directory yet. To get access, contact the organization owner.
|
||||
{% endif %}
|
||||
|
||||
Accept invitation using this link:
|
||||
|
||||
@ -179,7 +179,7 @@
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div
|
||||
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
||||
Hi{% if user-name %} {{ user-name|abbreviate:25 }}{% endif %},
|
||||
Hi,
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -188,8 +188,16 @@
|
||||
<div
|
||||
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
||||
"{{ organization-name|abbreviate:50 }}" has set up single sign-on (SSO) in Penpot. Access to its
|
||||
teams and files goes through your organization's identity provider. If you can't get in, your
|
||||
account probably isn't in the directory yet. To get access, contact the organization owner.
|
||||
teams and files now goes through your organization's identity provider.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div
|
||||
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
||||
If you can't get in, your account probably isn't in the directory yet. To get access, contact the
|
||||
organization owner.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -1 +1 @@
|
||||
“{{ organization-name|abbreviate:25 }}” has set up single sign-on (SSO) in Penpot
|
||||
“{{ organization-name|abbreviate:25 }}” uses single sign-on
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
Hello!
|
||||
Hi,
|
||||
|
||||
"{{ organization-name|abbreviate:50 }}" has set up single sign-on (SSO) in Penpot. Access to its teams and files goes
|
||||
through your organization's identity provider. If you can't get in, your account probably isn't in the directory yet. To get access, contact the organization owner.
|
||||
"{{ organization-name|abbreviate:50 }}" has set up single sign-on (SSO) in Penpot. Access to its teams and files now goes
|
||||
through your organization's identity provider.
|
||||
|
||||
If you can't get in, your account probably isn't in the directory yet. To get access, contact the organization owner.
|
||||
|
||||
The Penpot team.
|
||||
|
||||
@ -766,25 +766,22 @@
|
||||
(when-not (str/blank? value) value))
|
||||
|
||||
(defn org-sso-discovery-uri
|
||||
"Return the OIDC discovery URI from an org SSO config, preferring :issuer."
|
||||
"Return the OIDC discovery URI from an org SSO config."
|
||||
[sso]
|
||||
(or (non-blank-uri (:issuer sso))
|
||||
(non-blank-uri (:base-url sso))))
|
||||
(non-blank-uri (:issuer sso)))
|
||||
|
||||
(defn prepare-org-sso-provider
|
||||
"Build an OIDC provider map dynamically from the Nitrate org SSO config.
|
||||
Uses OIDC discovery via :base-url (or :issuer as fallback) when
|
||||
token/auth/user URIs are absent."
|
||||
[cfg {:keys [client-id client-secret base-url issuer scopes]}]
|
||||
Uses OIDC discovery via :issuer when token/auth/user URIs are absent."
|
||||
[cfg {:keys [client-id client-secret issuer]}]
|
||||
(prepare-oidc-provider cfg
|
||||
{:type "oidc"
|
||||
:client-id client-id
|
||||
:client-secret client-secret
|
||||
:base-uri (some-> (or (non-blank-uri base-url)
|
||||
(non-blank-uri issuer))
|
||||
:base-uri (some-> (non-blank-uri issuer)
|
||||
(str/rtrim "/")
|
||||
(str "/"))
|
||||
:scopes (into default-oidc-scopes (or scopes #{}))
|
||||
:scopes default-oidc-scopes
|
||||
:skip-ssrf-check? true}))
|
||||
|
||||
(defn build-org-sso-auth-redirect-uri
|
||||
@ -797,7 +794,7 @@
|
||||
(when-not issuer
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-sso-config
|
||||
:hint "missing issuer or base-url"))
|
||||
:hint "missing issuer"))
|
||||
(let [oidc-provider (or provider (prepare-org-sso-provider cfg sso))
|
||||
state-token (tokens/generate cfg {:iss "oidc"
|
||||
:dest-url dest-url
|
||||
|
||||
@ -455,7 +455,6 @@
|
||||
|
||||
(def ^:private schema:organization-setup-sso
|
||||
[:map
|
||||
[:user-name {:optional true} [:maybe ::sm/text]]
|
||||
[:organization-name ::sm/text]])
|
||||
|
||||
(def organization-setup-sso
|
||||
|
||||
@ -561,7 +561,7 @@
|
||||
(call cfg :get-org-sso-by-team {:team-id team-id}))]
|
||||
(if-not (:active sso)
|
||||
{:authorized true :sso sso}
|
||||
(if (or (:issuer sso) (:base-url sso))
|
||||
(if-not (str/blank? (:issuer sso))
|
||||
(let [props (:props session)
|
||||
sso-map (get props :sso {})
|
||||
organization-id (:organization-id sso)
|
||||
|
||||
@ -663,7 +663,7 @@
|
||||
(sv/defmethod ::check-nitrate-sso
|
||||
"Check if a user needs to login into the organization SSO.
|
||||
Accepts either team-id (to look up the org via the team) or organization-id directly.
|
||||
Returns {:authorized true} when SSO is not active.
|
||||
Returns {:authorized true} when SSO is not active or the user cannot access the team.
|
||||
Returns {:authorized false :redirect-uri <url>} when SSO is active;
|
||||
the client must redirect there. The OIDC provider itself handles
|
||||
re-authentication transparently if the user already has an active SSO session."
|
||||
@ -671,17 +671,22 @@
|
||||
::doc/added "2.19"
|
||||
::sm/params schema:check-nitrate-sso
|
||||
::nitrate/sso false}
|
||||
[cfg {:keys [team-id organization-id url] :as params}]
|
||||
[cfg {:keys [::rpc/profile-id team-id organization-id url] :as params}]
|
||||
(if (contains? cf/flags :nitrate)
|
||||
(let [request (rph/get-request params)
|
||||
{:keys [authorized sso]} (nitrate/sso-session-authorized? cfg organization-id team-id request)]
|
||||
(if authorized
|
||||
{:authorized true}
|
||||
(if (oidc/org-sso-discovery-uri sso)
|
||||
{:authorized false
|
||||
:redirect-uri (oidc/build-org-sso-auth-redirect-uri cfg sso
|
||||
:dest-url url
|
||||
:organization-id organization-id)}
|
||||
{:authorized false
|
||||
:redirect-uri nil})))
|
||||
(if (and team-id
|
||||
(not (teams/has-read-permissions? cfg profile-id team-id)))
|
||||
;; Let the destination RPC enforce its own permissions. Starting SSO before
|
||||
;; access is established sends unrelated users through the organization's IdP.
|
||||
{:authorized true}
|
||||
(let [request (rph/get-request params)
|
||||
{:keys [authorized sso]} (nitrate/sso-session-authorized? cfg organization-id team-id request)]
|
||||
(if authorized
|
||||
{:authorized true}
|
||||
(if (oidc/org-sso-discovery-uri sso)
|
||||
{:authorized false
|
||||
:redirect-uri (oidc/build-org-sso-auth-redirect-uri cfg sso
|
||||
:dest-url url
|
||||
:organization-id organization-id)}
|
||||
{:authorized false
|
||||
:redirect-uri nil}))))
|
||||
{:authorized true}))
|
||||
|
||||
@ -946,14 +946,14 @@ RETURNING id, deleted_at;")
|
||||
::sm/params [:map
|
||||
[:organization-id ::sm/uuid]
|
||||
[:updated-props ::sm/boolean]
|
||||
[:became-active ::sm/boolean]]
|
||||
[:announce-activation ::sm/boolean]]
|
||||
::rpc/auth false}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [organization-id updated-props became-active]}]
|
||||
[{:keys [::db/pool] :as cfg} {:keys [organization-id updated-props announce-activation]}]
|
||||
(when updated-props
|
||||
(rpc/invalidate-org-sso-cache-by-org! organization-id)
|
||||
(session/clear-org-sso-sessions! pool organization-id))
|
||||
(notifications/notify-organization-change-sso cfg organization-id)
|
||||
(when became-active
|
||||
(when announce-activation
|
||||
(neh/send-organization-setup-sso-emails! cfg organization-id))
|
||||
nil)
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
AND deleted_at IS NULL")
|
||||
|
||||
(def ^:private sql:get-profiles-by-emails
|
||||
"SELECT id, email, fullname, is_muted
|
||||
"SELECT id, email, is_muted
|
||||
FROM profile
|
||||
WHERE email = ANY(?)
|
||||
AND deleted_at IS NULL")
|
||||
@ -37,7 +37,7 @@
|
||||
(def ^:private xf:map-email (map :email))
|
||||
|
||||
(defn- recipients-by-emails
|
||||
"Build `{:email :user-name :profile}` maps for a deduplicated email list."
|
||||
"Build `{:email :profile}` maps for a deduplicated email list."
|
||||
[conn emails]
|
||||
(let [profiles (if (seq emails)
|
||||
(let [emails-array (db/create-array conn "text" emails)]
|
||||
@ -47,20 +47,18 @@
|
||||
(map (fn [email]
|
||||
(let [profile (get profile-by-email (str/lower email))]
|
||||
{:email email
|
||||
:user-name (:fullname profile)
|
||||
:profile profile}))
|
||||
emails)))
|
||||
|
||||
(defn- send-organization-setup-sso-email!
|
||||
"Send the organization SSO setup email to a single recipient, when allowed."
|
||||
[conn organization-name {:keys [email user-name profile]}]
|
||||
[conn organization-name {:keys [email profile]}]
|
||||
(when (or (nil? profile)
|
||||
(eml/allow-send-emails? conn profile))
|
||||
(eml/send! {::eml/conn conn
|
||||
::eml/factory eml/organization-setup-sso
|
||||
:public-uri (cf/get :public-uri)
|
||||
:to email
|
||||
:user-name user-name
|
||||
:organization-name organization-name})))
|
||||
|
||||
(defn- get-org-sso-notify-recipients
|
||||
|
||||
@ -1588,7 +1588,7 @@
|
||||
params {::th/type :notify-organization-sso-change
|
||||
:organization-id org-id
|
||||
:updated-props false
|
||||
:became-active true}]
|
||||
:announce-activation true}]
|
||||
|
||||
;; --- Setup: insert invitations ---
|
||||
;; Member also has a pending invitation: should still receive only one email.
|
||||
@ -1645,7 +1645,7 @@
|
||||
params {::th/type :notify-organization-sso-change
|
||||
:organization-id (uuid/random)
|
||||
:updated-props false
|
||||
:became-active false}]
|
||||
:announce-activation false}]
|
||||
(with-redefs [eml/send! (fn [params] (swap! sent conj params))]
|
||||
(th/management-command! params))
|
||||
(t/is (empty? @sent))))
|
||||
@ -1658,7 +1658,7 @@
|
||||
:organization-id org-id
|
||||
:client-id "test-client"
|
||||
:client-secret "test-secret"
|
||||
:base-url "https://idp.example.com"}))]
|
||||
:issuer "https://idp.example.com"}))]
|
||||
(t/is (th/success? out))
|
||||
(t/is (true? (-> out :result :valid)))))
|
||||
|
||||
@ -1671,7 +1671,7 @@
|
||||
(t/is (th/success? out))
|
||||
(t/is (false? (-> out :result :valid)))))
|
||||
|
||||
(t/deftest check-organization-sso-uses-issuer-when-base-url-is-blank
|
||||
(t/deftest check-organization-sso-passes-issuer-to-validation
|
||||
(let [org-id (uuid/random)
|
||||
out (with-redefs [oidc/is-organization-sso-config-valid?
|
||||
(fn [_cfg sso]
|
||||
@ -1682,7 +1682,6 @@
|
||||
:organization-id org-id
|
||||
:client-id "test-client"
|
||||
:client-secret "test-secret"
|
||||
:base-url ""
|
||||
:issuer "https://idp.example.com/"}))]
|
||||
(t/is (th/success? out))
|
||||
(t/is (true? (-> out :result :valid)))))
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
(ns backend-tests.rpc-nitrate-test
|
||||
(:require
|
||||
[app.auth.oidc :as oidc]
|
||||
[app.common.json :as json]
|
||||
[app.common.time :as ct]
|
||||
[app.common.uuid :as uuid]
|
||||
@ -59,10 +60,105 @@
|
||||
:get-org-summary org-summary
|
||||
nil)))
|
||||
|
||||
(defn- active-sso-call-mock
|
||||
[team-id organization-id organization-owner-id]
|
||||
(fn [_cfg method params]
|
||||
(case method
|
||||
:get-team-org
|
||||
(when (= team-id (:team-id params))
|
||||
{:id team-id
|
||||
:organization {:id organization-id
|
||||
:owner-id organization-owner-id}})
|
||||
|
||||
:get-org-sso-by-team
|
||||
{:active true
|
||||
:issuer "https://idp.example.com"
|
||||
:organization-id organization-id}
|
||||
|
||||
nil)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Tests
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(t/deftest check-nitrate-sso-skips-gate-without-team-access
|
||||
(let [team-owner (th/create-profile* 1 {:is-active true})
|
||||
external-profile (th/create-profile* 2 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id team-owner)})
|
||||
organization-id (uuid/random)
|
||||
params (with-meta
|
||||
{::th/type :check-nitrate-sso
|
||||
::rpc/profile-id (:id external-profile)
|
||||
:team-id (:id team)
|
||||
:url "https://penpot.example.com/#/workspace"}
|
||||
{::http/request {}})]
|
||||
(binding [cf/flags (conj cf/flags :nitrate)]
|
||||
(with-redefs [nitrate/call
|
||||
(active-sso-call-mock
|
||||
(:id team)
|
||||
organization-id
|
||||
(:id team-owner))
|
||||
oidc/build-org-sso-auth-redirect-uri
|
||||
(constantly "https://idp.example.com/authorize")]
|
||||
(let [out (th/command! params)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= {:authorized true} (:result out))))))))
|
||||
|
||||
(t/deftest check-nitrate-sso-keeps-gate-for-team-member
|
||||
(let [team-owner (th/create-profile* 1 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id team-owner)})
|
||||
organization-id (uuid/random)
|
||||
redirect-uri "https://idp.example.com/authorize"
|
||||
params (with-meta
|
||||
{::th/type :check-nitrate-sso
|
||||
::rpc/profile-id (:id team-owner)
|
||||
:team-id (:id team)
|
||||
:url "https://penpot.example.com/#/workspace"}
|
||||
{::http/request {}})]
|
||||
(binding [cf/flags (conj cf/flags :nitrate)]
|
||||
(with-redefs [nitrate/call
|
||||
(active-sso-call-mock
|
||||
(:id team)
|
||||
organization-id
|
||||
(:id team-owner))
|
||||
oidc/build-org-sso-auth-redirect-uri
|
||||
(constantly redirect-uri)]
|
||||
(let [out (th/command! params)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= {:authorized false
|
||||
:redirect-uri redirect-uri}
|
||||
(:result out))))))))
|
||||
|
||||
(t/deftest check-nitrate-sso-keeps-gate-for-non-member-org-owner
|
||||
(let [team-owner (th/create-profile* 1 {:is-active true})
|
||||
org-owner (th/create-profile* 2 {:is-active true})
|
||||
team (th/create-team* 1 {:profile-id (:id team-owner)})
|
||||
organization-id (uuid/random)
|
||||
redirect-uri "https://idp.example.com/authorize"
|
||||
params (with-meta
|
||||
{::th/type :check-nitrate-sso
|
||||
::rpc/profile-id (:id org-owner)
|
||||
:team-id (:id team)
|
||||
:url "https://penpot.example.com/#/workspace"}
|
||||
{::http/request {}})]
|
||||
(binding [cf/flags (conj cf/flags :nitrate)]
|
||||
(with-redefs [nitrate/organization-owner-of-team?
|
||||
(fn [_cfg profile-id team-id]
|
||||
(and (= (:id org-owner) profile-id)
|
||||
(= (:id team) team-id)))
|
||||
nitrate/call
|
||||
(active-sso-call-mock
|
||||
(:id team)
|
||||
organization-id
|
||||
(:id org-owner))
|
||||
oidc/build-org-sso-auth-redirect-uri
|
||||
(constantly redirect-uri)]
|
||||
(let [out (th/command! params)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= {:authorized false
|
||||
:redirect-uri redirect-uri}
|
||||
(:result out))))))))
|
||||
|
||||
(t/deftest leave-org-happy-path-no-extra-teams
|
||||
(let [profile-owner (th/create-profile* 1 {:is-active true})
|
||||
profile-user (th/create-profile* 2 {:is-active true})
|
||||
|
||||
@ -70,7 +70,5 @@
|
||||
[:active {:optional true} [:maybe :boolean]]
|
||||
[:provider {:optional true} [:maybe :string]]
|
||||
[:client-id {:optional true} [:maybe :string]]
|
||||
[:base-url {:optional true} [:maybe :string]]
|
||||
[:client-secret {:optional true} [:maybe :string]]
|
||||
[:issuer {:optional true} [:maybe :string]]
|
||||
[:scopes {:optional true} [:maybe [::sm/set ::sm/text]]]])
|
||||
[:issuer {:optional true} [:maybe :string]]])
|
||||
|
||||
@ -687,20 +687,35 @@
|
||||
(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)]
|
||||
organization (:organization team)
|
||||
current-team? (= (:id team) current-team-id)]
|
||||
(when (and (contains? cf/flags :nitrate)
|
||||
notification
|
||||
(= (:id team) current-team-id))
|
||||
(rx/of (ntf/show {:content (tr notification (:name organization))
|
||||
:type :toast
|
||||
:level :info
|
||||
:timeout nil})))))
|
||||
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)
|
||||
@ -767,14 +782,7 @@
|
||||
team (dm/get-in state [:teams team-id])
|
||||
org-id (dm/get-in team [:organization :id])]
|
||||
(when (= organization-id org-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)))))))))))))
|
||||
(check-team-sso team-id)))))))
|
||||
|
||||
(defn- process-message
|
||||
[{:keys [type] :as msg}]
|
||||
|
||||
50
frontend/test/frontend_tests/data/dashboard_test.cljs
Normal file
50
frontend/test/frontend_tests/data/dashboard_test.cljs
Normal file
@ -0,0 +1,50 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns frontend-tests.data.dashboard-test
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.mock :as mock]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(t/deftest moving-current-team-into-sso-organization-redirects
|
||||
(t/async done
|
||||
(let [team-id (uuid/next)
|
||||
organization {:id (uuid/next) :name "OrgA"}
|
||||
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
|
||||
{:team {:id team-id :organization organization}
|
||||
:notification nil})]
|
||||
(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))))
|
||||
@ -8,6 +8,7 @@
|
||||
[frontend-tests.code-gen-style-test]
|
||||
[frontend-tests.composable-tests.comp.sync-test]
|
||||
[frontend-tests.copy-as-svg-test]
|
||||
[frontend-tests.data.dashboard-test]
|
||||
[frontend-tests.data.nitrate-test]
|
||||
[frontend-tests.data.repo-test]
|
||||
[frontend-tests.data.store-test]
|
||||
@ -96,6 +97,7 @@
|
||||
'frontend-tests.code-gen-style-test
|
||||
'frontend-tests.composable-tests.comp.sync-test
|
||||
'frontend-tests.copy-as-svg-test
|
||||
'frontend-tests.data.dashboard-test
|
||||
'frontend-tests.data.nitrate-test
|
||||
'frontend-tests.data.repo-test
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user