Add a way to extend the UI with an Java API (#23772)

* POC to see how we could extend the UI

This is very crude and there are still open issues that need to be worked out

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* added saving option

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* added list and recreate client form

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* add tab ui

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* integrate tabs

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* remove examples

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* fixed error messages

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* added Feature for ui customization

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

---------

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Erik Jan de Wit 2024-01-22 09:19:58 +01:00 committed by GitHub
parent 96c882447d
commit 37790c7956
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 729 additions and 155 deletions

View file

@ -105,8 +105,11 @@ public class Profile {
MULTI_SITE("Multi-site support", Type.PREVIEW),
OFFLINE_SESSION_PRELOADING("Offline session preloading", Type.DEPRECATED),
HOSTNAME_V1("Hostname Options V1", Type.DEFAULT),
//HOSTNAME_V2("Hostname Options V2", Type.DEFAULT, 2),
DECLARATIVE_UI("declarative ui spi", Type.EXPERIMENTAL),
;
private final Type type;

View file

@ -78,6 +78,7 @@ public class ProfileTest {
Profile.Feature.DYNAMIC_SCOPES,
Profile.Feature.DOCKER,
Profile.Feature.MULTI_SITE,
Profile.Feature.DECLARATIVE_UI,
Profile.Feature.RECOVERY_CODES,
Profile.Feature.SCRIPTS,
Profile.Feature.TOKEN_EXCHANGE,

View file

@ -2987,3 +2987,15 @@ termsAndConditionsUserAttribute=Terms and conditions accepted timestamp
realmOverridesDescription= Realm overrides allow you to specify translations that will take effect for the entire realm. These translations will override any translation specified by a theme.
addTranslation=Add translation
effectiveMessageBundlesDescription=An effective message bundle is the set of translations for a given language, theme, and theme type. It also takes into account any realm overrides, which will take precedence.
clientsClientScopesHelp=The scopes associated with this resource.
searchItem=Search item
createItem=Create item
itemDelete=Delete item
itemDeleteConfirm=Are you sure you want to permanently delete the item
itemDeleteConfirmTitle=Delete item?
itemDeletedSuccess=The item has been deleted
itemDeleteError=Could not delete item: {{error}}
noItems=There are no items
noItemsInstructions=You haven't created any items in this realm. Create a item to get started.
itemSaveError=Error could not save item\! {{error}}
itemSaveSuccessful=Sucessful saved

View file

@ -24,6 +24,7 @@ import { AuthWall } from "./root/AuthWall";
const AppContexts = ({ children }: PropsWithChildren) => (
<ErrorBoundaryProvider>
<ServerInfoProvider>
<RealmsProvider>
<RealmContextProvider>
<WhoAmIContextProvider>
@ -39,6 +40,7 @@ const AppContexts = ({ children }: PropsWithChildren) => (
</WhoAmIContextProvider>
</RealmContextProvider>
</RealmsProvider>
</ServerInfoProvider>
</ErrorBoundaryProvider>
);
@ -53,13 +55,11 @@ export const App = () => {
mainContainerId={mainPageContentId}
>
<ErrorBoundaryFallback fallback={ErrorRenderer}>
<ServerInfoProvider>
<Suspense fallback={<KeycloakSpinner />}>
<AuthWall>
<Outlet />
</AuthWall>
</Suspense>
</ServerInfoProvider>
</ErrorBoundaryFallback>
</Page>
</AppContexts>

View file

@ -9,23 +9,25 @@ import {
import { FormEvent } from "react";
import { useTranslation } from "react-i18next";
import { NavLink, useMatch, useNavigate } from "react-router-dom";
import { RealmSelector } from "./components/realm-selector/RealmSelector";
import { useAccess } from "./context/access/Access";
import { useRealm } from "./context/realm-context/RealmContext";
import { useServerInfo } from "./context/server-info/ServerInfoProvider";
import { toPage } from "./page/routes";
import { AddRealmRoute } from "./realm/routes/AddRealm";
import { routes } from "./routes";
import "./page-nav.css";
type LeftNavProps = { title: string; path: string };
type LeftNavProps = { title: string; path: string; id?: string };
const LeftNav = ({ title, path }: LeftNavProps) => {
const LeftNav = ({ title, path, id }: LeftNavProps) => {
const { t } = useTranslation();
const { hasAccess } = useAccess();
const { realm } = useRealm();
const route = routes.find(
(route) => route.path.replace(/\/:.+?(\?|(?:(?!\/).)*|$)/g, "") === path,
(route) =>
route.path.replace(/\/:.+?(\?|(?:(?!\/).)*|$)/g, "") === (id || path),
);
const accessAllowed =
@ -56,6 +58,9 @@ const LeftNav = ({ title, path }: LeftNavProps) => {
export const PageNav = () => {
const { t } = useTranslation();
const { hasSomeAccess } = useAccess();
const { componentTypes } = useServerInfo();
const pages =
componentTypes?.["org.keycloak.services.ui.extend.UiPageProvider"];
const navigate = useNavigate();
@ -116,6 +121,14 @@ export const PageNav = () => {
<LeftNav title="authentication" path="/authentication" />
<LeftNav title="identityProviders" path="/identity-providers" />
<LeftNav title="userFederation" path="/user-federation" />
{pages?.map((p) => (
<LeftNav
key={p.id}
title={p.id}
path={toPage({ providerId: p.id }).pathname!}
id="/page-section"
/>
))}
</NavGroup>
)}
</Nav>

View file

@ -1,4 +1,5 @@
import {
Tab,
TabProps,
Tabs,
TabsComponent,
@ -6,11 +7,22 @@ import {
} from "@patternfly/react-core";
import {
Children,
isValidElement,
JSXElementConstructor,
PropsWithChildren,
ReactElement,
isValidElement,
} from "react";
import { Path, useHref, useLocation } from "react-router-dom";
import {
Path,
generatePath,
matchPath,
useHref,
useLocation,
useParams,
} from "react-router-dom";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { PageHandler } from "../../page/PageHandler";
import { TAB_PROVIDER } from "../../page/PageList";
// TODO: Remove the custom 'children' props and type once the following issue has been resolved:
// https://github.com/patternfly/patternfly-react/issues/6766
@ -32,14 +44,31 @@ export const RoutableTabs = ({
...otherProps
}: RoutableTabsProps) => {
const { pathname } = useLocation();
const params = useParams();
const { componentTypes } = useServerInfo();
const tabs = componentTypes?.[TAB_PROVIDER] || [];
// Extract event keys from children.
const matchedTabs = tabs
.filter((tab) => matchPath({ path: tab.metadata.path }, pathname))
.map((t) => ({
...t,
pathname: generatePath(t.metadata.path, {
...params,
...t.metadata.params,
}),
}));
// Extract all keys from matchedTabs
const matchedTabsKeys = matchedTabs.map((t) => t.pathname);
// Extract event keys from children
const eventKeys = Children.toArray(children)
.filter((child): child is ChildElement => isValidElement(child))
.map((child) => child.props.eventKey.toString());
const allKeys = [...eventKeys, ...matchedTabsKeys];
// Determine if there is an exact match.
const exactMatch = eventKeys.find(
const exactMatch = allKeys.find(
(eventKey) => eventKey === decodeURI(pathname),
);
@ -63,10 +92,33 @@ export const RoutableTabs = ({
{...otherProps}
>
{children}
{matchedTabs.map((t) => (
<DynamicTab key={t.id} eventKey={t.pathname} title={t.id}>
<PageHandler page={t} providerType={TAB_PROVIDER} />
</DynamicTab>
))}
</Tabs>
);
};
type DynamicTabProps = {
title: string;
eventKey: string;
};
const DynamicTab = ({
children,
...props
}: PropsWithChildren<DynamicTabProps>) => {
const href = useHref(props.eventKey);
return (
<Tab href={href} {...props}>
{children}
</Tab>
);
};
export const useRoutableTab = (to: Partial<Path>) => ({
eventKey: to.pathname ?? "",
href: useHref(to),

View file

@ -0,0 +1,67 @@
import { ButtonVariant, DropdownItem } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import { adminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { PageHandler } from "./PageHandler";
import { PAGE_PROVIDER } from "./PageList";
import { PageParams, toPage } from "./routes";
import { useRealm } from "../context/realm-context/RealmContext";
export default function Page() {
const { t } = useTranslation();
const { componentTypes } = useServerInfo();
const { realm } = useRealm();
const pages = componentTypes?.[PAGE_PROVIDER];
const navigate = useNavigate();
const { id, providerId } = useParams<PageParams>();
const { addAlert, addError } = useAlerts();
const page = pages?.find((p) => p.id === providerId);
if (!page) {
throw new Error(t("notFound"));
}
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "itemDeleteConfirmTitle",
messageKey: "itemDeleteConfirm",
continueButtonLabel: "delete",
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
await adminClient.components.del({
id: id!,
});
addAlert(t("itemDeletedSuccess"));
navigate(toPage({ realm, providerId: providerId! }));
} catch (error) {
addError("itemSaveError", error);
}
},
});
return (
<>
<DeleteConfirm />
<ViewHeader
titleKey={id || t("createItem")}
dropdownItems={
id
? [
<DropdownItem
data-testid="delete-item"
key="delete"
onClick={() => toggleDeleteDialog()}
>
{t("delete")}
</DropdownItem>,
]
: undefined
}
/>
<PageHandler providerType={PAGE_PROVIDER} id={id} page={page} />
</>
);
}

View file

@ -0,0 +1,107 @@
import ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation";
import RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { ActionGroup, Button, Form, PageSection } from "@patternfly/react-core";
import { useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { adminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { DynamicComponents } from "../components/dynamic/DynamicComponents";
import { useRealm } from "../context/realm-context/RealmContext";
import { useFetch } from "../utils/useFetch";
import { PAGE_PROVIDER, TAB_PROVIDER } from "./PageList";
import { toPage } from "./routes";
type PageHandlerProps = {
id?: string;
providerType: typeof TAB_PROVIDER | typeof PAGE_PROVIDER;
page: ComponentTypeRepresentation;
};
export const PageHandler = ({
id: idAttribute,
providerType,
page: { id: providerId, ...page },
}: PageHandlerProps) => {
const { t } = useTranslation();
const form = useForm<ComponentTypeRepresentation>();
const { realm: realmName } = useRealm();
const [realm, setRealm] = useState<RealmRepresentation>();
const { addAlert, addError } = useAlerts();
const [id, setId] = useState(idAttribute);
useFetch(
async () =>
await Promise.all([
adminClient.realms.findOne({ realm: realmName }),
id ? adminClient.components.findOne({ id }) : Promise.resolve(),
providerType === TAB_PROVIDER
? adminClient.components.find({ type: TAB_PROVIDER })
: Promise.resolve(),
]),
([realm, data, tabs]) => {
setRealm(realm);
const tab = (tabs || []).find((t) => t.providerId === providerId);
form.reset(data || tab || {});
if (tab) setId(tab.id);
},
[],
);
const onSubmit = async (component: ComponentRepresentation) => {
if (component.config)
Object.entries(component.config).forEach(
([key, value]) =>
(component.config![key] = Array.isArray(value) ? value : [value]),
);
try {
const updatedComponent = {
...component,
providerId,
providerType,
parentId: realm?.id,
};
if (id) {
await adminClient.components.update({ id }, updatedComponent);
} else {
await adminClient.components.create(updatedComponent);
}
addAlert("itemSaveSuccessful");
} catch (error) {
addError("itemSaveError", error);
}
};
return (
<PageSection variant="light">
<Form
isHorizontal
onSubmit={form.handleSubmit(onSubmit)}
className="keycloak__form"
>
<FormProvider {...form}>
<DynamicComponents properties={page.properties} />
</FormProvider>
<ActionGroup>
<Button data-testid="save" type="submit">
{t("save")}
</Button>
<Button
variant="link"
component={(props) => (
<Link
{...props}
to={toPage({ realm: realmName, providerId: providerId! })}
/>
)}
>
{t("cancel")}
</Button>
</ActionGroup>
</Form>
</PageSection>
);
};

View file

@ -0,0 +1,140 @@
import ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import type { ComponentQuery } from "@keycloak/keycloak-admin-client/lib/resources/components";
import {
Button,
ButtonVariant,
PageSection,
ToolbarItem,
} from "@patternfly/react-core";
import { IRowData } from "@patternfly/react-table";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useNavigate, useParams } from "react-router-dom";
import { adminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { useFetch } from "../utils/useFetch";
import { PageListParams, toDetailPage } from "./routes";
export const PAGE_PROVIDER = "org.keycloak.services.ui.extend.UiPageProvider";
export const TAB_PROVIDER = "org.keycloak.services.ui.extend.UiTabProvider";
const DetailLink = (obj: ComponentRepresentation) => {
const { realm } = useRealm();
return (
<Link
key={obj.id}
to={toDetailPage({ realm, providerId: obj.providerId!, id: obj.id! })}
>
{obj.id}
</Link>
);
};
export default function PageList() {
const { t } = useTranslation();
const { addAlert, addError } = useAlerts();
const navigate = useNavigate();
const { providerId } = useParams<PageListParams>();
const [key, setKey] = useState(0);
const refresh = () => setKey(key + 1);
const { realm: realmName } = useRealm();
const [realm, setRealm] = useState<RealmRepresentation>();
const [selectedItem, setSelectedItem] = useState<ComponentRepresentation>();
const { componentTypes } = useServerInfo();
const pages = componentTypes?.[PAGE_PROVIDER];
const page = pages?.find((p) => p.id === providerId)!;
useFetch(
async () => adminClient.realms.findOne({ realm: realmName }),
setRealm,
[],
);
const loader = async () => {
const params: ComponentQuery = {
parent: realm?.id,
type: PAGE_PROVIDER,
};
return await adminClient.components.find({ ...params });
};
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "itemDeleteConfirmTitle",
messageKey: "itemDeleteConfirm",
continueButtonLabel: "delete",
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
await adminClient.components.del({
id: selectedItem!.id!,
});
addAlert(t("itemDeletedSuccess"));
refresh();
} catch (error) {
addError("itemSaveError", error);
}
},
});
return (
<PageSection variant="light" className="pf-u-p-0">
<DeleteConfirm />
<ViewHeader titleKey={page.id} subKey={page.helpText} divider={false} />
<KeycloakDataTable
key={key}
toolbarItem={
<ToolbarItem>
<Button
component={(props) => (
<Link
{...props}
to={toDetailPage({ realm: realmName, providerId: page.id })}
/>
)}
>
{t("createItem")}
</Button>
</ToolbarItem>
}
actionResolver={(item: IRowData) => [
{
title: t("delete"),
onClick() {
setSelectedItem(item.data);
toggleDeleteDialog();
},
},
]}
searchPlaceholderKey="searchItem"
loader={loader}
columns={[
{ name: "id", cellRenderer: DetailLink },
...page.properties.slice(0, 3).map((p) => ({
name: `config.${p.name}[0]`,
displayKey: p.label,
})),
]}
ariaLabelKey="list"
emptyState={
<ListEmptyState
hasIcon
message={t("noItems")}
instructions={t("noItemsInstructions")}
primaryActionText={t("createItem")}
onPrimaryAction={() =>
navigate(toDetailPage({ realm: realmName, providerId: page.id }))
}
/>
}
/>
</PageSection>
);
}

View file

@ -0,0 +1,39 @@
import { Path, generatePath } from "react-router-dom";
import type { AppRouteObject } from "../routes";
import { lazy } from "react";
export type PageListParams = { realm?: string; providerId: string };
export type PageParams = { realm: string; providerId: string; id?: string };
const PageList = lazy(() => import("./PageList"));
const Page = lazy(() => import("./Page"));
const PageListRoute: AppRouteObject = {
path: "/:realm?/page-section/:providerId",
element: <PageList />,
breadcrumb: (t) => t("page"),
handle: {
access: "view-realm",
},
};
const PageDetailRoute: AppRouteObject = {
path: "/:realm/page/:providerId/:id?",
element: <Page />,
breadcrumb: (t) => t("page"),
handle: {
access: "view-realm",
},
};
const routes: AppRouteObject[] = [PageListRoute, PageDetailRoute];
export const toPage = (params: PageListParams): Partial<Path> => ({
pathname: generatePath(PageListRoute.path, params),
});
export const toDetailPage = (params: PageParams): Partial<Path> => ({
pathname: generatePath(PageDetailRoute.path, params),
});
export default routes;

View file

@ -18,6 +18,7 @@ import realmRoutes from "./realm/routes";
import sessionRoutes from "./sessions/routes";
import userFederationRoutes from "./user-federation/routes";
import userRoutes from "./user/routes";
import pageRoutes from "./page/routes";
export type AppRouteObjectHandle = {
access: AccessType | AccessType[];
@ -51,6 +52,7 @@ export const routes: AppRouteObject[] = [
...userRoutes,
...groupsRoutes,
...dashboardRoutes,
...pageRoutes,
NotFoundRoute,
];

View file

@ -64,20 +64,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
HTTP(S):

View file

@ -64,20 +64,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
HTTP(S):

View file

@ -59,20 +59,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Config:

View file

@ -59,20 +59,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Config:

View file

@ -59,20 +59,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Config:

View file

@ -59,20 +59,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Config:

View file

@ -89,20 +89,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Hostname:

View file

@ -89,20 +89,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1],impersonation[:v1], js-adapter[:v1], kerberos
[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:
v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1],
step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1],
update-email[:v1], web-authn[:v1].
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Hostname:

View file

@ -89,20 +89,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Hostname:

View file

@ -89,20 +89,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1],impersonation[:v1], js-adapter[:v1], kerberos
[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:
v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1],
step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1],
update-email[:v1], web-authn[:v1].
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Hostname:

View file

@ -90,20 +90,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Hostname:

View file

@ -90,20 +90,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1],impersonation[:v1], js-adapter[:v1], kerberos
[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:
v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1],
step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1],
update-email[:v1], web-authn[:v1].
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Hostname:

View file

@ -90,20 +90,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter
[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Hostname:

View file

@ -90,20 +90,21 @@ Feature:
--features <feature> Enables a set of one or more features. Possible values are: account-api[:v1],
account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1],
admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1],
client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1],
dynamic-scopes[:v1], fips[:v1], hostname[:v1],impersonation[:v1], js-adapter[:v1], kerberos
[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:
v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1],
step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1],
update-email[:v1], web-authn[:v1].
client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[:
v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation
[:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1],
offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1],
scripts[:v1], step-up-authentication[:v1], token-exchange[:v1],
transient-users[:v1], update-email[:v1], web-authn[:v1].
--features-disabled <feature>
Disables a set of one or more features. Possible values are: account-api,
account2, account3, admin-api, admin-fine-grained-authz, admin2,
authorization, ciba, client-policies, client-secret-rotation, device-flow,
docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos,
linkedin-oauth, multi-site, offline-session-preloading, par, preview,
recovery-codes, scripts, step-up-authentication, token-exchange,
transient-users, update-email, web-authn.
authorization, ciba, client-policies, client-secret-rotation,
declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips,
impersonation, js-adapter, kerberos, linkedin-oauth, multi-site,
offline-session-preloading, par, preview, recovery-codes, scripts,
step-up-authentication, token-exchange, transient-users, update-email,
web-authn.
Hostname:

View file

@ -0,0 +1,10 @@
package org.keycloak.services.ui.extend;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.Provider;
import java.util.List;
public interface UiPageProvider extends Provider, ConfiguredProvider {
}

View file

@ -0,0 +1,12 @@
package org.keycloak.services.ui.extend;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderFactory;
public interface UiPageProviderFactory<T> extends ComponentFactory<T, UiPageProvider> {
default T create(KeycloakSession session, ComponentModel model) {
return null;
}
}

View file

@ -0,0 +1,33 @@
package org.keycloak.services.ui.extend;
import org.keycloak.common.Profile;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
public class UiPageSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "ui-page";
}
@Override
public Class<? extends Provider> getProviderClass() {
return UiPageProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return UiPageProviderFactory.class;
}
@Override
public boolean isEnabled() {
return Profile.isFeatureEnabled(Profile.Feature.DECLARATIVE_UI);
}
}

View file

@ -0,0 +1,8 @@
package org.keycloak.services.ui.extend;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.Provider;
public interface UiTabProvider extends Provider, ConfiguredProvider {
}

View file

@ -0,0 +1,26 @@
package org.keycloak.services.ui.extend;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import java.util.HashMap;
import java.util.Map;
public interface UiTabProviderFactory<T> extends ComponentFactory<T, UiTabProvider> {
default T create(KeycloakSession session, ComponentModel model) {
return null;
}
@Override
default Map<String, Object> getTypeMetadata() {
Map<String, Object> metadata = new HashMap<>();
metadata.put("path", getPath());
metadata.put("params", getParams());
return metadata;
}
String getPath();
Map<String, String> getParams();
}

View file

@ -0,0 +1,33 @@
package org.keycloak.services.ui.extend;
import org.keycloak.common.Profile;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
public class UiTabSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "ui-tab";
}
@Override
public Class<? extends Provider> getProviderClass() {
return UiTabProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return UiTabProviderFactory.class;
}
@Override
public boolean isEnabled() {
return Profile.isFeatureEnabled(Profile.Feature.DECLARATIVE_UI);
}
}

View file

@ -38,6 +38,8 @@ org.keycloak.scripting.ScriptingSpi
org.keycloak.services.managers.BruteForceProtectorSpi
org.keycloak.services.resource.AccountResourceSpi
org.keycloak.services.resource.RealmResourceSPI
org.keycloak.services.ui.extend.UiPageSpi
org.keycloak.services.ui.extend.UiTabSpi
org.keycloak.sessions.AuthenticationSessionSpi
org.keycloak.sessions.StickySessionEncoderSpi
org.keycloak.protocol.ClientInstallationSpi