From 18f8055cbf5f7664c4a47b3ab48354e506c9bf26 Mon Sep 17 00:00:00 2001 From: sebferrer Date: Wed, 16 Nov 2022 18:07:32 +0000 Subject: [PATCH] feat(saml): saml 2.0 implementation Signed-off-by: sebferrer Co-authored-by: ThibaultHerard --- package-lock.json | 86 ++++++++++++++++++- src/react-components/ory/helpers/node.tsx | 2 +- src/react-components/ory/helpers/utils.ts | 2 + .../ory/sections/saml-section.tsx | 36 ++++++++ .../ory/sections/saml-settings-section.tsx | 28 ++++++ src/react-components/ory/user-auth-card.tsx | 10 +++ .../ory/user-settings-card.tsx | 10 +++ src/stories/Ory/AuthSettings.stories.tsx | 7 ++ 8 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 src/react-components/ory/sections/saml-section.tsx create mode 100644 src/react-components/ory/sections/saml-settings-section.tsx diff --git a/package-lock.json b/package-lock.json index 5f318ccc8..8fb85e826 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7804,6 +7804,29 @@ "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", "dev": true, "hasInstallScript": true, + "dependencies": { + "@esbuild/linux-loong64": "0.14.54", + "esbuild-android-64": "0.14.54", + "esbuild-android-arm64": "0.14.54", + "esbuild-darwin-64": "0.14.54", + "esbuild-darwin-arm64": "0.14.54", + "esbuild-freebsd-64": "0.14.54", + "esbuild-freebsd-arm64": "0.14.54", + "esbuild-linux-32": "0.14.54", + "esbuild-linux-64": "0.14.54", + "esbuild-linux-arm": "0.14.54", + "esbuild-linux-arm64": "0.14.54", + "esbuild-linux-mips64le": "0.14.54", + "esbuild-linux-ppc64le": "0.14.54", + "esbuild-linux-riscv64": "0.14.54", + "esbuild-linux-s390x": "0.14.54", + "esbuild-netbsd-64": "0.14.54", + "esbuild-openbsd-64": "0.14.54", + "esbuild-sunos-64": "0.14.54", + "esbuild-windows-32": "0.14.54", + "esbuild-windows-64": "0.14.54", + "esbuild-windows-arm64": "0.14.54" + }, "bin": { "esbuild": "bin/esbuild" }, @@ -7880,6 +7903,7 @@ "dev": true, "dependencies": { "esbuild": "^0.14.27", + "fsevents": "~2.3.2", "postcss": "^8.4.13", "resolve": "^1.22.0", "rollup": ">=2.59.0 <2.78.0" @@ -8097,6 +8121,9 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, + "dependencies": { + "graceful-fs": "^4.1.6" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -13531,6 +13558,7 @@ "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", + "fsevents": "~2.3.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -13763,6 +13791,7 @@ "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", "dev": true, "dependencies": { + "@colors/colors": "1.5.0", "string-width": "^4.2.0" }, "engines": { @@ -16537,7 +16566,8 @@ "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2", - "optionator": "^0.8.1" + "optionator": "^0.8.1", + "source-map": "~0.6.1" }, "bin": { "escodegen": "bin/escodegen.js", @@ -19000,6 +19030,7 @@ "minimist": "^1.2.5", "neo-async": "^2.6.0", "source-map": "^0.6.1", + "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" }, "bin": { @@ -20935,6 +20966,7 @@ "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", "jest-regex-util": "^26.0.0", "jest-serializer": "^26.6.2", @@ -21342,6 +21374,7 @@ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "dependencies": { + "graceful-fs": "^4.1.6", "universalify": "^2.0.0" }, "optionalDependencies": { @@ -22596,6 +22629,7 @@ "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", "dev": true, "dependencies": { + "encoding": "^0.1.13", "minipass": "^3.1.6", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" @@ -22946,6 +22980,19 @@ "dev": true, "dependencies": { "@next/env": "12.2.5", + "@next/swc-android-arm-eabi": "12.2.5", + "@next/swc-android-arm64": "12.2.5", + "@next/swc-darwin-arm64": "12.2.5", + "@next/swc-darwin-x64": "12.2.5", + "@next/swc-freebsd-x64": "12.2.5", + "@next/swc-linux-arm-gnueabihf": "12.2.5", + "@next/swc-linux-arm64-gnu": "12.2.5", + "@next/swc-linux-arm64-musl": "12.2.5", + "@next/swc-linux-x64-gnu": "12.2.5", + "@next/swc-linux-x64-musl": "12.2.5", + "@next/swc-win32-arm64-msvc": "12.2.5", + "@next/swc-win32-ia32-msvc": "12.2.5", + "@next/swc-win32-x64-msvc": "12.2.5", "@swc/helpers": "0.4.3", "caniuse-lite": "^1.0.30001332", "postcss": "8.4.14", @@ -26042,6 +26089,7 @@ "dev": true, "dependencies": { "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", "read-package-json": "^2.0.0", "readdir-scoped-modules": "^1.0.0", "semver": "2 || 3 || 4 || 5", @@ -27103,6 +27151,9 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.3.tgz", "integrity": "sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==", "dev": true, + "dependencies": { + "fsevents": "~2.3.2" + }, "bin": { "rollup": "dist/bin/rollup" }, @@ -30662,6 +30713,7 @@ "dev": true, "dependencies": { "esbuild": "^0.14.47", + "fsevents": "~2.3.2", "postcss": "^8.4.16", "resolve": "^1.22.1", "rollup": ">=2.75.6 <2.77.0 || ~2.77.0" @@ -30807,6 +30859,29 @@ "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", "dev": true, "hasInstallScript": true, + "dependencies": { + "@esbuild/linux-loong64": "0.14.54", + "esbuild-android-64": "0.14.54", + "esbuild-android-arm64": "0.14.54", + "esbuild-darwin-64": "0.14.54", + "esbuild-darwin-arm64": "0.14.54", + "esbuild-freebsd-64": "0.14.54", + "esbuild-freebsd-arm64": "0.14.54", + "esbuild-linux-32": "0.14.54", + "esbuild-linux-64": "0.14.54", + "esbuild-linux-arm": "0.14.54", + "esbuild-linux-arm64": "0.14.54", + "esbuild-linux-mips64le": "0.14.54", + "esbuild-linux-ppc64le": "0.14.54", + "esbuild-linux-riscv64": "0.14.54", + "esbuild-linux-s390x": "0.14.54", + "esbuild-netbsd-64": "0.14.54", + "esbuild-openbsd-64": "0.14.54", + "esbuild-sunos-64": "0.14.54", + "esbuild-windows-32": "0.14.54", + "esbuild-windows-64": "0.14.54", + "esbuild-windows-arm64": "0.14.54" + }, "bin": { "esbuild": "bin/esbuild" }, @@ -31031,6 +31106,7 @@ "anymatch": "^2.0.0", "async-each": "^1.0.1", "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", "inherits": "^2.0.3", "is-binary-path": "^1.0.0", @@ -31842,8 +31918,10 @@ "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", "dev": true, "dependencies": { + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" }, "optionalDependencies": { "chokidar": "^3.4.1", @@ -32188,6 +32266,9 @@ "resolved": "https://registry.npmjs.org/x-default-browser/-/x-default-browser-0.4.0.tgz", "integrity": "sha512-7LKo7RtWfoFN/rHx1UELv/2zHGMx8MkZKDq1xENmOCTkfIqZJ0zZ26NEJX8czhnPXVcqS0ARjjfJB+eJ0/5Cvw==", "dev": true, + "dependencies": { + "default-browser-id": "^1.0.4" + }, "bin": { "x-default-browser": "bin/x-default-browser.js" }, @@ -32273,6 +32354,7 @@ "integrity": "sha512-sGvEcBOTNum68x9jCpCVGPFJ6mWnkD0YxOcddDlJHRx3tKdB2q8pCHExMVZo/AV/6geuVJXG7hljDaWG8+5GDw==", "dev": true, "dependencies": { + "commander": "^2.20.3", "lodash.get": "^4.4.2", "lodash.isequal": "^4.5.0", "validator": "^13.7.0" diff --git a/src/react-components/ory/helpers/node.tsx b/src/react-components/ory/helpers/node.tsx index 901bcb998..d9161782a 100644 --- a/src/react-components/ory/helpers/node.tsx +++ b/src/react-components/ory/helpers/node.tsx @@ -116,7 +116,7 @@ export const Node = ({ const isSocial = (attrs.name === "provider" || attrs.name === "link") && - node.group === "oidc" + (node.group === "oidc" || node.group === "saml") const submit: ButtonSubmit = { type: attrs.type as "submit" | "reset" | "button" | undefined, diff --git a/src/react-components/ory/helpers/utils.ts b/src/react-components/ory/helpers/utils.ts index 5f7cccdf0..0d5048d8f 100644 --- a/src/react-components/ory/helpers/utils.ts +++ b/src/react-components/ory/helpers/utils.ts @@ -5,6 +5,8 @@ import { UiNode } from "@ory/client" export const hasOidc = (nodes: UiNode[]) => nodes.some(({ group }) => group === "oidc") + export const hasSaml = (nodes: UiNode[]) => + nodes.some(({ group }) => group === "saml") export const hasPassword = (nodes: UiNode[]) => nodes.some(({ group }) => group === "password") export const hasWebauthn = (nodes: UiNode[]) => diff --git a/src/react-components/ory/sections/saml-section.tsx b/src/react-components/ory/sections/saml-section.tsx new file mode 100644 index 000000000..db379d72a --- /dev/null +++ b/src/react-components/ory/sections/saml-section.tsx @@ -0,0 +1,36 @@ +import { filterNodesByGroups } from "@ory/integrations/ui" +import { gridStyle } from "../../../theme" +import { FilterFlowNodes } from "../helpers/filter-flow-nodes" +import { SelfServiceFlow } from "../helpers/types" +import { hasSaml } from "../helpers/utils" + +export const SAMLSection = (flow: SelfServiceFlow): JSX.Element | null => { + return hasSaml(flow.ui.nodes) ? ( +
+ {filterNodesByGroups({ + nodes: flow.ui.nodes, + groups: "saml", + withoutDefaultGroup: true, + }).some((node) => node.messages.length > 0) && ( +
+ {/* we need other SAML fields here such as input fields when an SAML registration flow occured and it redirects us back to complete the missing traits */} + +
+ )} + + +
+ ) : null +} diff --git a/src/react-components/ory/sections/saml-settings-section.tsx b/src/react-components/ory/sections/saml-settings-section.tsx new file mode 100644 index 000000000..ba3d28354 --- /dev/null +++ b/src/react-components/ory/sections/saml-settings-section.tsx @@ -0,0 +1,28 @@ +import { SelfServiceSettingsFlow } from "@ory/client" +import { gridStyle } from "../../../theme" +import { FilterFlowNodes } from "../helpers/filter-flow-nodes" +import { hasSaml } from "../helpers/utils" + +export type SAMLSettingsProps = { + flow: SelfServiceSettingsFlow + title?: string +} + +export const SAMLSettingsSection = ({ + flow, +}: SAMLSettingsProps): JSX.Element | null => { + const filter = { + nodes: flow.ui.nodes, + groups: "saml", + withoutDefaultGroup: true, + } + + return hasSaml(flow.ui.nodes) ? ( +
+ +
+ ) : null +} diff --git a/src/react-components/ory/user-auth-card.tsx b/src/react-components/ory/user-auth-card.tsx index 329b76097..8349daf67 100644 --- a/src/react-components/ory/user-auth-card.tsx +++ b/src/react-components/ory/user-auth-card.tsx @@ -23,6 +23,7 @@ import { import { LinkSection } from "./sections/link-section" import { LoginSection } from "./sections/login-section" import { OIDCSection } from "./sections/oidc-section" +import { SAMLSection } from "./sections/saml-section" import { PasswordlessSection } from "./sections/passwordless-section" import { RegistrationSection } from "./sections/registration-section" @@ -90,6 +91,7 @@ export const UserAuthCard = ({ let $flow = null let $oidc = null + let $saml = null let $passwordless: JSX.Element | null = null let message: MessageSectionProps | null = null @@ -200,6 +202,7 @@ export const UserAuthCard = ({ case "login": $passwordless = PasswordlessSection(flow) $oidc = OIDCSection(flow) + $saml = SAMLSection(flow) $flow = LoginSection({ nodes: flow.ui.nodes, @@ -223,6 +226,7 @@ export const UserAuthCard = ({ case "registration": $passwordless = PasswordlessSection(flow) $oidc = OIDCSection(flow) + $saml = SAMLSection(flow) $flow = RegistrationSection({ nodes: flow.ui.nodes, }) @@ -272,6 +276,12 @@ export const UserAuthCard = ({
{subtitle && {subtitle}} + {$saml && ( + <> + + {$saml} + + )} {$oidc && ( <> diff --git a/src/react-components/ory/user-settings-card.tsx b/src/react-components/ory/user-settings-card.tsx index d3e4136d9..ea1ce3868 100644 --- a/src/react-components/ory/user-settings-card.tsx +++ b/src/react-components/ory/user-settings-card.tsx @@ -9,12 +9,14 @@ import { import { hasLookupSecret, hasOidc, + hasSaml, hasPassword, hasTotp, hasWebauthn, } from "./helpers/utils" import { LookupSecretSettingsSection } from "./sections/lookup-secret-settings-section" import { OIDCSettingsSection } from "./sections/oidc-settings-section" +import { SAMLSettingsSection } from "./sections/saml-settings-section" import { PasswordSettingsSection } from "./sections/password-settings-section" import { ProfileSettingsSection } from "./sections/profile-settings-section" import { TOTPSettingsSection } from "./sections/totp-settings-section" @@ -26,6 +28,7 @@ export type UserSettingsFlowType = | "totp" | "webauthn" | "oidc" + | "saml" | "lookupSecret" export type UserSettingsCardProps = { @@ -84,6 +87,13 @@ export const UserSettingsCard = ({ $flow = } break + case "saml": + if (hasSaml(flow.ui.nodes)) { + hasFlow = true + cardTitle = title || "SSO Sign In" + $flow = + } + break case "totp": if (hasTotp(flow.ui.nodes)) { hasFlow = true diff --git a/src/stories/Ory/AuthSettings.stories.tsx b/src/stories/Ory/AuthSettings.stories.tsx index 924797590..6d77a3491 100644 --- a/src/stories/Ory/AuthSettings.stories.tsx +++ b/src/stories/Ory/AuthSettings.stories.tsx @@ -54,3 +54,10 @@ UserSettingsOidcCard.args = { flowType: "oidc", flow: settingsFlow as SelfServiceSettingsFlow, } + +export const UserSettingsSamlCard = Template.bind({}) + +UserSettingsSamlCard.args = { + flowType: "saml", + flow: settingsFlow as SelfServiceSettingsFlow, +}