OIDC: Check static client registration and add login flow (#11088)
* util functions to get static client id * check static client ids in login flow * remove dead code * add trailing slash * comment error enum * spacing * PR tidying * more comments * add ValidatedDelegatedAuthConfig type * Update src/Login.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/Login.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/utils/ValidatedServerConfig.ts Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * rename oidc_static_clients to oidc_static_client_ids * comment --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
parent
35f8c525aa
commit
328db8fdfd
10 changed files with 456 additions and 45 deletions
|
@ -17,19 +17,29 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react";
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import { DELEGATED_OIDC_COMPATIBILITY, IdentityProviderBrand } from "matrix-js-sdk/src/@types/auth";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import SdkConfig from "../../../../src/SdkConfig";
|
||||
import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
|
||||
import Login from "../../../../src/components/structures/auth/Login";
|
||||
import BasePlatform from "../../../../src/BasePlatform";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import { Features } from "../../../../src/settings/Settings";
|
||||
import { ValidatedDelegatedAuthConfig } from "../../../../src/utils/ValidatedServerConfig";
|
||||
import * as registerClientUtils from "../../../../src/utils/oidc/registerClient";
|
||||
import { OidcClientError } from "../../../../src/utils/oidc/error";
|
||||
|
||||
jest.mock("matrix-js-sdk/src/matrix");
|
||||
|
||||
jest.useRealTimers();
|
||||
|
||||
const oidcStaticClientsConfig = {
|
||||
"https://staticallyregisteredissuer.org/": "static-clientId-123",
|
||||
};
|
||||
|
||||
describe("Login", function () {
|
||||
let platform: MockedObject<BasePlatform>;
|
||||
|
||||
|
@ -42,6 +52,7 @@ describe("Login", function () {
|
|||
SdkConfig.put({
|
||||
brand: "test-brand",
|
||||
disable_custom_urls: true,
|
||||
oidc_static_client_ids: oidcStaticClientsConfig,
|
||||
});
|
||||
mockClient.login.mockClear().mockResolvedValue({});
|
||||
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
|
||||
|
@ -51,6 +62,7 @@ describe("Login", function () {
|
|||
return mockClient;
|
||||
});
|
||||
fetchMock.resetBehavior();
|
||||
fetchMock.resetHistory();
|
||||
fetchMock.get("https://matrix.org/_matrix/client/versions", {
|
||||
unstable_features: {},
|
||||
versions: [],
|
||||
|
@ -66,10 +78,14 @@ describe("Login", function () {
|
|||
unmockPlatformPeg();
|
||||
});
|
||||
|
||||
function getRawComponent(hsUrl = "https://matrix.org", isUrl = "https://vector.im") {
|
||||
function getRawComponent(
|
||||
hsUrl = "https://matrix.org",
|
||||
isUrl = "https://vector.im",
|
||||
delegatedAuthentication?: ValidatedDelegatedAuthConfig,
|
||||
) {
|
||||
return (
|
||||
<Login
|
||||
serverConfig={mkServerConfig(hsUrl, isUrl)}
|
||||
serverConfig={mkServerConfig(hsUrl, isUrl, delegatedAuthentication)}
|
||||
onLoggedIn={() => {}}
|
||||
onRegisterClick={() => {}}
|
||||
onServerConfigChange={() => {}}
|
||||
|
@ -77,8 +93,8 @@ describe("Login", function () {
|
|||
);
|
||||
}
|
||||
|
||||
function getComponent(hsUrl?: string, isUrl?: string) {
|
||||
return render(getRawComponent(hsUrl, isUrl));
|
||||
function getComponent(hsUrl?: string, isUrl?: string, delegatedAuthentication?: ValidatedDelegatedAuthConfig) {
|
||||
return render(getRawComponent(hsUrl, isUrl, delegatedAuthentication));
|
||||
}
|
||||
|
||||
it("should show form with change server link", async () => {
|
||||
|
@ -190,6 +206,7 @@ describe("Login", function () {
|
|||
versions: [],
|
||||
});
|
||||
rerender(getRawComponent("https://server2"));
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||
|
||||
fireEvent.click(container.querySelector(".mx_SSOButton")!);
|
||||
expect(platform.startSingleSignOn.mock.calls[1][0].baseUrl).toBe("https://server2");
|
||||
|
@ -319,4 +336,117 @@ describe("Login", function () {
|
|||
// error cleared
|
||||
expect(screen.queryByText("Your test-brand is misconfigured")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("OIDC native flow", () => {
|
||||
const hsUrl = "https://matrix.org";
|
||||
const isUrl = "https://vector.im";
|
||||
const issuer = "https://test.com/";
|
||||
const delegatedAuth = {
|
||||
issuer,
|
||||
registrationEndpoint: issuer + "register",
|
||||
tokenEndpoint: issuer + "token",
|
||||
authorizationEndpoint: issuer + "authorization",
|
||||
};
|
||||
beforeEach(() => {
|
||||
jest.spyOn(logger, "error");
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === Features.OidcNativeFlow,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.spyOn(logger, "error").mockRestore();
|
||||
});
|
||||
|
||||
it("should not attempt registration when oidc native flow setting is disabled", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
|
||||
getComponent(hsUrl, isUrl, delegatedAuth);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||
|
||||
// continued with normal setup
|
||||
expect(mockClient.loginFlows).toHaveBeenCalled();
|
||||
// normal password login rendered
|
||||
expect(screen.getByLabelText("Username")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should attempt to register oidc client", async () => {
|
||||
// dont mock, spy so we can check config values were correctly passed
|
||||
jest.spyOn(registerClientUtils, "getOidcClientId");
|
||||
getComponent(hsUrl, isUrl, delegatedAuth);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||
|
||||
// called with values from config
|
||||
expect(registerClientUtils.getOidcClientId).toHaveBeenCalledWith(
|
||||
delegatedAuth,
|
||||
"test-brand",
|
||||
"http://localhost",
|
||||
oidcStaticClientsConfig,
|
||||
);
|
||||
});
|
||||
|
||||
it("should fallback to normal login when client does not have static clientId", async () => {
|
||||
getComponent(hsUrl, isUrl, delegatedAuth);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(new Error(OidcClientError.DynamicRegistrationNotSupported));
|
||||
|
||||
// continued with normal setup
|
||||
expect(mockClient.loginFlows).toHaveBeenCalled();
|
||||
// normal password login rendered
|
||||
expect(screen.getByLabelText("Username")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// short term during active development, UI will be added in next PRs
|
||||
it("should show error when oidc native flow is correctly configured but not supported by UI", async () => {
|
||||
const delegatedAuthWithStaticClientId = {
|
||||
...delegatedAuth,
|
||||
issuer: "https://staticallyregisteredissuer.org/",
|
||||
};
|
||||
getComponent(hsUrl, isUrl, delegatedAuthWithStaticClientId);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||
|
||||
// did not continue with matrix login
|
||||
expect(mockClient.loginFlows).not.toHaveBeenCalled();
|
||||
// no oidc native UI yet
|
||||
expect(
|
||||
screen.getByText("This homeserver doesn't offer any login flows which are supported by this client."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
/**
|
||||
* Oidc-aware flows still work while the oidc-native feature flag is disabled
|
||||
*/
|
||||
it("should show oidc-aware flow for oidc-enabled homeserver when oidc native flow setting is disabled", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
mockClient.loginFlows.mockResolvedValue({
|
||||
flows: [
|
||||
{
|
||||
type: "m.login.sso",
|
||||
[DELEGATED_OIDC_COMPATIBILITY.name]: true,
|
||||
},
|
||||
{
|
||||
type: "m.login.password",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { container } = getComponent(hsUrl, isUrl, delegatedAuth);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading…"));
|
||||
|
||||
// continued with normal setup
|
||||
expect(mockClient.loginFlows).toHaveBeenCalled();
|
||||
// oidc-aware 'continue' button displayed
|
||||
const ssoButtons = container.querySelectorAll(".mx_SSOButton");
|
||||
expect(ssoButtons.length).toBe(1);
|
||||
expect(ssoButtons[0].textContent).toBe("Continue");
|
||||
// no password form visible
|
||||
expect(container.querySelector("form")).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue