Delabs native OIDC support (#28615)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>pull/28791/head
parent
b07d10cb23
commit
db02f26005
|
@ -30,7 +30,6 @@ import AuthHeader from "../../views/auth/AuthHeader";
|
||||||
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
|
||||||
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
|
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
|
||||||
import { filterBoolean } from "../../../utils/arrays";
|
import { filterBoolean } from "../../../utils/arrays";
|
||||||
import { Features } from "../../../settings/Settings";
|
|
||||||
import { startOidcLogin } from "../../../utils/oidc/authorize";
|
import { startOidcLogin } from "../../../utils/oidc/authorize";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -90,7 +89,6 @@ type OnPasswordLogin = {
|
||||||
*/
|
*/
|
||||||
export default class LoginComponent extends React.PureComponent<IProps, IState> {
|
export default class LoginComponent extends React.PureComponent<IProps, IState> {
|
||||||
private unmounted = false;
|
private unmounted = false;
|
||||||
private oidcNativeFlowEnabled = false;
|
|
||||||
private loginLogic!: Login;
|
private loginLogic!: Login;
|
||||||
|
|
||||||
private readonly stepRendererMap: Record<string, () => ReactNode>;
|
private readonly stepRendererMap: Record<string, () => ReactNode>;
|
||||||
|
@ -98,9 +96,6 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
public constructor(props: IProps) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
// only set on a config level, so we don't need to watch
|
|
||||||
this.oidcNativeFlowEnabled = SettingsStore.getValue(Features.OidcNativeFlow);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
busy: false,
|
busy: false,
|
||||||
errorText: null,
|
errorText: null,
|
||||||
|
@ -358,10 +353,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
|
|
||||||
const loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
|
const loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
|
||||||
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
||||||
// if native OIDC is enabled in the client pass the server's delegated auth settings
|
delegatedAuthentication: this.props.serverConfig.delegatedAuthentication,
|
||||||
delegatedAuthentication: this.oidcNativeFlowEnabled
|
|
||||||
? this.props.serverConfig.delegatedAuthentication
|
|
||||||
: undefined,
|
|
||||||
});
|
});
|
||||||
this.loginLogic = loginLogic;
|
this.loginLogic = loginLogic;
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@ import { AuthHeaderDisplay } from "./header/AuthHeaderDisplay";
|
||||||
import { AuthHeaderProvider } from "./header/AuthHeaderProvider";
|
import { AuthHeaderProvider } from "./header/AuthHeaderProvider";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
|
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
|
||||||
import { Features } from "../../../settings/Settings";
|
|
||||||
import { startOidcLogin } from "../../../utils/oidc/authorize";
|
import { startOidcLogin } from "../../../utils/oidc/authorize";
|
||||||
|
|
||||||
const debuglog = (...args: any[]): void => {
|
const debuglog = (...args: any[]): void => {
|
||||||
|
@ -130,8 +129,6 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
private readonly loginLogic: Login;
|
private readonly loginLogic: Login;
|
||||||
// `replaceClient` tracks latest serverConfig to spot when it changes under the async method which fetches flows
|
// `replaceClient` tracks latest serverConfig to spot when it changes under the async method which fetches flows
|
||||||
private latestServerConfig?: ValidatedServerConfig;
|
private latestServerConfig?: ValidatedServerConfig;
|
||||||
// cache value from settings store
|
|
||||||
private oidcNativeFlowEnabled = false;
|
|
||||||
|
|
||||||
public constructor(props: IProps) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -150,14 +147,10 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
serverDeadError: "",
|
serverDeadError: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// only set on a config level, so we don't need to watch
|
|
||||||
this.oidcNativeFlowEnabled = SettingsStore.getValue(Features.OidcNativeFlow);
|
|
||||||
|
|
||||||
const { hsUrl, isUrl, delegatedAuthentication } = this.props.serverConfig;
|
const { hsUrl, isUrl, delegatedAuthentication } = this.props.serverConfig;
|
||||||
this.loginLogic = new Login(hsUrl, isUrl, null, {
|
this.loginLogic = new Login(hsUrl, isUrl, null, {
|
||||||
defaultDeviceDisplayName: "Element login check", // We shouldn't ever be used
|
defaultDeviceDisplayName: "Element login check", // We shouldn't ever be used
|
||||||
// if native OIDC is enabled in the client pass the server's delegated auth settings
|
delegatedAuthentication,
|
||||||
delegatedAuthentication: this.oidcNativeFlowEnabled ? delegatedAuthentication : undefined,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,10 +220,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
this.loginLogic.setHomeserverUrl(hsUrl);
|
this.loginLogic.setHomeserverUrl(hsUrl);
|
||||||
this.loginLogic.setIdentityServerUrl(isUrl);
|
this.loginLogic.setIdentityServerUrl(isUrl);
|
||||||
// if native OIDC is enabled in the client pass the server's delegated auth settings
|
this.loginLogic.setDelegatedAuthentication(serverConfig.delegatedAuthentication);
|
||||||
const delegatedAuthentication = this.oidcNativeFlowEnabled ? serverConfig.delegatedAuthentication : undefined;
|
|
||||||
|
|
||||||
this.loginLogic.setDelegatedAuthentication(delegatedAuthentication);
|
|
||||||
|
|
||||||
let ssoFlow: SSOFlow | undefined;
|
let ssoFlow: SSOFlow | undefined;
|
||||||
let oidcNativeFlow: OidcNativeFlow | undefined;
|
let oidcNativeFlow: OidcNativeFlow | undefined;
|
||||||
|
|
|
@ -1461,8 +1461,6 @@
|
||||||
"notification_settings_beta_caption": "Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.",
|
"notification_settings_beta_caption": "Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.",
|
||||||
"notification_settings_beta_title": "Notification Settings",
|
"notification_settings_beta_title": "Notification Settings",
|
||||||
"notifications": "Enable the notifications panel in the room header",
|
"notifications": "Enable the notifications panel in the room header",
|
||||||
"oidc_native_flow": "OIDC native authentication",
|
|
||||||
"oidc_native_flow_description": "⚠ WARNING: Experimental. Use OIDC native authentication when supported by the server.",
|
|
||||||
"release_announcement": "Release announcement",
|
"release_announcement": "Release announcement",
|
||||||
"render_reaction_images": "Render custom images in reactions",
|
"render_reaction_images": "Render custom images in reactions",
|
||||||
"render_reaction_images_description": "Sometimes referred to as \"custom emojis\".",
|
"render_reaction_images_description": "Sometimes referred to as \"custom emojis\".",
|
||||||
|
|
|
@ -86,7 +86,6 @@ export enum LabGroup {
|
||||||
|
|
||||||
export enum Features {
|
export enum Features {
|
||||||
NotificationSettings2 = "feature_notification_settings2",
|
NotificationSettings2 = "feature_notification_settings2",
|
||||||
OidcNativeFlow = "feature_oidc_native_flow",
|
|
||||||
ReleaseAnnouncement = "feature_release_announcement",
|
ReleaseAnnouncement = "feature_release_announcement",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,15 +437,6 @@ export const SETTINGS: { [setting: string]: ISetting } = {
|
||||||
shouldWarn: true,
|
shouldWarn: true,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
[Features.OidcNativeFlow]: {
|
|
||||||
isFeature: true,
|
|
||||||
labsGroup: LabGroup.Developer,
|
|
||||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED,
|
|
||||||
supportedLevelsAreOrdered: true,
|
|
||||||
displayName: _td("labs|oidc_native_flow"),
|
|
||||||
description: _td("labs|oidc_native_flow_description"),
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* @deprecated in favor of {@link fontSizeDelta}
|
* @deprecated in favor of {@link fontSizeDelta}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,9 +19,6 @@ import { TestSdkContext } from "../../TestSdkContext";
|
||||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||||
import LogoutDialog from "../../../../src/components/views/dialogs/LogoutDialog";
|
import LogoutDialog from "../../../../src/components/views/dialogs/LogoutDialog";
|
||||||
import Modal from "../../../../src/Modal";
|
import Modal from "../../../../src/Modal";
|
||||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
|
||||||
import { Features } from "../../../../src/settings/Settings";
|
|
||||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
|
||||||
import { mockOpenIdConfiguration } from "../../../test-utils/oidc";
|
import { mockOpenIdConfiguration } from "../../../test-utils/oidc";
|
||||||
import { Action } from "../../../../src/dispatcher/actions";
|
import { Action } from "../../../../src/dispatcher/actions";
|
||||||
import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
|
import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
|
||||||
|
@ -137,7 +134,6 @@ describe("<UserMenu>", () => {
|
||||||
isCrossSigningReady: jest.fn().mockResolvedValue(true),
|
isCrossSigningReady: jest.fn().mockResolvedValue(true),
|
||||||
exportSecretsBundle: jest.fn().mockResolvedValue({}),
|
exportSecretsBundle: jest.fn().mockResolvedValue({}),
|
||||||
} as unknown as CryptoApi);
|
} as unknown as CryptoApi);
|
||||||
await SettingsStore.setValue(Features.OidcNativeFlow, null, SettingLevel.DEVICE, true);
|
|
||||||
const spy = jest.spyOn(defaultDispatcher, "dispatch");
|
const spy = jest.spyOn(defaultDispatcher, "dispatch");
|
||||||
|
|
||||||
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||||
|
|
|
@ -19,7 +19,6 @@ import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../../
|
||||||
import Login from "../../../../../src/components/structures/auth/Login";
|
import Login from "../../../../../src/components/structures/auth/Login";
|
||||||
import BasePlatform from "../../../../../src/BasePlatform";
|
import BasePlatform from "../../../../../src/BasePlatform";
|
||||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||||
import { Features } from "../../../../../src/settings/Settings";
|
|
||||||
import * as registerClientUtils from "../../../../../src/utils/oidc/registerClient";
|
import * as registerClientUtils from "../../../../../src/utils/oidc/registerClient";
|
||||||
import { makeDelegatedAuthConfig } from "../../../../test-utils/oidc";
|
import { makeDelegatedAuthConfig } from "../../../../test-utils/oidc";
|
||||||
|
|
||||||
|
@ -371,9 +370,6 @@ describe("Login", function () {
|
||||||
const delegatedAuth = makeDelegatedAuthConfig(issuer);
|
const delegatedAuth = makeDelegatedAuthConfig(issuer);
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(logger, "error");
|
jest.spyOn(logger, "error");
|
||||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
|
||||||
(settingName) => settingName === Features.OidcNativeFlow,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
|
@ -22,8 +22,6 @@ import {
|
||||||
} from "../../../../test-utils";
|
} from "../../../../test-utils";
|
||||||
import Registration from "../../../../../src/components/structures/auth/Registration";
|
import Registration from "../../../../../src/components/structures/auth/Registration";
|
||||||
import { makeDelegatedAuthConfig } from "../../../../test-utils/oidc";
|
import { makeDelegatedAuthConfig } from "../../../../test-utils/oidc";
|
||||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
|
||||||
import { Features } from "../../../../../src/settings/Settings";
|
|
||||||
import { startOidcLogin } from "../../../../../src/utils/oidc/authorize";
|
import { startOidcLogin } from "../../../../../src/utils/oidc/authorize";
|
||||||
|
|
||||||
jest.mock("../../../../../src/utils/oidc/authorize", () => ({
|
jest.mock("../../../../../src/utils/oidc/authorize", () => ({
|
||||||
|
@ -180,49 +178,29 @@ describe("Registration", function () {
|
||||||
fetchMock.get(authConfig.metadata.jwks_uri!, { keys: [] });
|
fetchMock.get(authConfig.metadata.jwks_uri!, { keys: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when oidc native flow is not enabled in settings", () => {
|
it("should display oidc-native continue button", async () => {
|
||||||
beforeEach(() => {
|
const { container } = getComponent(defaultHsUrl, defaultIsUrl, authConfig);
|
||||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||||
});
|
// no form
|
||||||
|
expect(container.querySelector("form")).toBeFalsy();
|
||||||
|
|
||||||
it("should display user/pass registration form", async () => {
|
expect(await screen.findByText("Continue")).toBeTruthy();
|
||||||
const { container } = getComponent(defaultHsUrl, defaultIsUrl, authConfig);
|
|
||||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
|
||||||
expect(container.querySelector("form")).toBeTruthy();
|
|
||||||
expect(mockClient.loginFlows).toHaveBeenCalled();
|
|
||||||
expect(mockClient.registerRequest).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when oidc native flow is enabled in settings", () => {
|
it("should start OIDC login flow as registration on button click", async () => {
|
||||||
beforeEach(() => {
|
getComponent(defaultHsUrl, defaultIsUrl, authConfig);
|
||||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((key) => key === Features.OidcNativeFlow);
|
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||||
});
|
|
||||||
|
|
||||||
it("should display oidc-native continue button", async () => {
|
fireEvent.click(await screen.findByText("Continue"));
|
||||||
const { container } = getComponent(defaultHsUrl, defaultIsUrl, authConfig);
|
|
||||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
|
||||||
// no form
|
|
||||||
expect(container.querySelector("form")).toBeFalsy();
|
|
||||||
|
|
||||||
expect(await screen.findByText("Continue")).toBeTruthy();
|
expect(startOidcLogin).toHaveBeenCalledWith(
|
||||||
});
|
authConfig,
|
||||||
|
clientId,
|
||||||
it("should start OIDC login flow as registration on button click", async () => {
|
defaultHsUrl,
|
||||||
getComponent(defaultHsUrl, defaultIsUrl, authConfig);
|
defaultIsUrl,
|
||||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
// isRegistration
|
||||||
|
true,
|
||||||
fireEvent.click(await screen.findByText("Continue"));
|
);
|
||||||
|
|
||||||
expect(startOidcLogin).toHaveBeenCalledWith(
|
|
||||||
authConfig,
|
|
||||||
clientId,
|
|
||||||
defaultHsUrl,
|
|
||||||
defaultIsUrl,
|
|
||||||
// isRegistration
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when is mobile registeration", () => {
|
describe("when is mobile registeration", () => {
|
||||||
|
|
Loading…
Reference in New Issue