created endpoint to query authentication section with used by information (#3225)

* created endpoint to query authentication section
with used by information

* buildIn !== builtIn
This commit is contained in:
Erik Jan de Wit 2022-09-13 14:36:41 +02:00 committed by GitHub
parent eb24eaa386
commit 33b9b95bd7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 470 additions and 143 deletions

View file

@ -73,20 +73,22 @@
"buildIn": "Built-in",
"appliedByProviders": "Applied by the following providers",
"appliedByClients": "Applied by the following clients",
"specificProviders": "Specific providers",
"specificClients": "Specific clients",
"default": "Default",
"notInUse": "Not in use",
"used": {
"SPECIFIC_PROVIDERS": "Specific providers",
"SPECIFIC_CLIENTS": "Specific clients",
"DEFAULT": "Default",
"notInUse": "Not in use"
},
"duplicate": "Duplicate",
"bindFlow": "Bind flow",
"chooseBindingType": "Choose binding type",
"flow": {
"browserFlow": "Browser flow",
"registrationFlow": "Registration flow",
"directGrantFlow": "Direct grant flow",
"resetCredentialsFlow": "Reset credentials flow",
"clientAuthenticationFlow": "Client authentication flow",
"dockerAuthenticationFlow": "Docker authentication flow"
"browser": "Browser flow",
"registration": "Registration flow",
"direct grant": "Direct grant flow",
"reset credentials": "Reset credentials flow",
"clients": "Client authentication flow",
"docker auth": "Docker authentication flow"
},
"editInfo": "Edit info",
"editFlow": "Edit flow",

View file

@ -36,23 +36,25 @@ import {
RoutableTabs,
} from "../components/routable-tabs/RoutableTabs";
import { AuthenticationTab, toAuthentication } from "./routes/Authentication";
import { addTrailingSlash } from "../util";
import { getAuthorizationHeaders } from "../utils/getAuthorizationHeaders";
import "./authentication-section.css";
type UsedBy = "specificClients" | "default" | "specificProviders";
type UsedBy = "SPECIFIC_CLIENTS" | "SPECIFIC_PROVIDERS" | "DEFAULT";
export type AuthenticationType = AuthenticationFlowRepresentation & {
usedBy: { type?: UsedBy; values: string[] };
usedBy?: { type?: UsedBy; values: string[] };
};
export const REALM_FLOWS = [
"browserFlow",
"registrationFlow",
"directGrantFlow",
"resetCredentialsFlow",
"clientAuthenticationFlow",
"dockerAuthenticationFlow",
];
export const REALM_FLOWS = new Map<string, string>([
["browserFlow", "browser"],
["registrationFlow", "registration"],
["directGrantFlow", "direct grant"],
["resetCredentialsFlow", "reset credentials"],
["clientAuthenticationFlow", "clients"],
["dockerAuthenticationFlow", "docker auth"],
]);
export default function AuthenticationSection() {
const { t } = useTranslation("authentication");
@ -68,54 +70,18 @@ export default function AuthenticationSection() {
const [bindFlowOpen, toggleBindFlow] = useToggle();
const loader = async () => {
const [allClients, allIdps, realmRep, flows] = await Promise.all([
adminClient.clients.find(),
adminClient.identityProviders.find(),
adminClient.realms.findOne({ realm }),
adminClient.authenticationManagement.getFlows(),
]);
if (!realmRep) {
throw new Error(t("common:notFound"));
const flowsRequest = await fetch(
`${addTrailingSlash(
adminClient.baseUrl
)}admin/realms/${realm}/admin-ui-authentication-management/flows`,
{
method: "GET",
headers: getAuthorizationHeaders(await adminClient.getAccessToken()),
}
const defaultFlows = Object.entries(realmRep).filter(([key]) =>
REALM_FLOWS.includes(key)
);
const flows = await flowsRequest.json();
for (const flow of flows as AuthenticationType[]) {
flow.usedBy = { values: [] };
const clients = allClients.filter(
(client) =>
client.authenticationFlowBindingOverrides &&
(client.authenticationFlowBindingOverrides["direct_grant"] ===
flow.id ||
client.authenticationFlowBindingOverrides["browser"] === flow.id)
);
if (clients.length > 0) {
flow.usedBy.type = "specificClients";
flow.usedBy.values = clients.map(({ clientId }) => clientId!);
}
const idps = allIdps.filter(
(idp) =>
idp.firstBrokerLoginFlowAlias === flow.alias ||
idp.postBrokerLoginFlowAlias === flow.alias
);
if (idps.length > 0) {
flow.usedBy.type = "specificProviders";
flow.usedBy.values = idps.map(({ alias }) => alias!);
}
const defaultFlow = defaultFlows.find(
([, alias]) => flow.alias === alias
);
if (defaultFlow) {
flow.usedBy.type = "default";
flow.usedBy.values.push(defaultFlow[0]);
}
}
return sortBy(flows as AuthenticationType[], (flow) => flow.usedBy.type);
return sortBy(flows as AuthenticationType[], (flow) => flow.usedBy?.type);
};
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
@ -156,7 +122,7 @@ export default function AuthenticationSection() {
to={toFlow({
realm,
id: id!,
usedBy: usedBy.type || "notInUse",
usedBy: usedBy?.type || "notInUse",
builtIn: builtIn ? "builtIn" : undefined,
})}
key={`link-${id}`}
@ -236,7 +202,7 @@ export default function AuthenticationSection() {
setSelectedFlow(data);
},
},
...(data.usedBy.type !== "default" &&
...(data.usedBy?.type !== "DEFAULT" &&
data.providerId !== "client-flow"
? [
{
@ -248,7 +214,7 @@ export default function AuthenticationSection() {
},
]
: []),
...(!data.builtIn && data.usedBy.values.length === 0
...(!data.builtIn && !data.usedBy
? [
{
title: t("common:delete"),

View file

@ -84,7 +84,7 @@ export const BindFlowDialog = ({ flowAlias, onClose }: BindFlowDialogProps) => {
<FormGroup label={t("chooseBindingType")} fieldId="chooseBindingType">
<Controller
name="bindingType"
defaultValue={REALM_FLOWS[0]}
defaultValue={"browserFlow"}
control={control}
render={({ onChange, value }) => (
<Select
@ -100,17 +100,20 @@ export const BindFlowDialog = ({ flowAlias, onClose }: BindFlowDialogProps) => {
isOpen={open}
menuAppendTo="parent"
>
{REALM_FLOWS.filter(
(f) => f !== "dockerAuthenticationFlow"
).map((flow) => (
{[...REALM_FLOWS.keys()]
.filter((f) => f !== "dockerAuthenticationFlow")
.map((key) => {
const value = REALM_FLOWS.get(key);
return (
<SelectOption
selected={flow === value}
key={flow}
value={flow}
selected={key === REALM_FLOWS.get(key)}
key={key}
value={key}
>
{t(`flow.${flow}`)}
{t(`flow.${value}`)}
</SelectOption>
))}
);
})}
</Select>
)}
/>

View file

@ -303,7 +303,7 @@ export default function FlowDetails() {
<ViewHeader
titleKey={flow?.alias || ""}
badges={[
{ text: <Label>{t(usedBy)}</Label> },
{ text: <Label>{t(`used.${usedBy}`)}</Label> },
builtIn
? {
text: (

View file

@ -10,9 +10,11 @@ import {
} from "@patternfly/react-core";
import { CheckCircleIcon } from "@patternfly/react-icons";
import type { AuthenticationType } from "../AuthenticationSection";
import { AuthenticationType, REALM_FLOWS } from "../AuthenticationSection";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import useToggle from "../../utils/useToggle";
import { useAdminClient } from "../../context/auth/AdminClient";
import { fetchUsedBy } from "../../components/role-mapping/resource";
import "./used-by.css";
@ -28,17 +30,31 @@ const Label = ({ label }: { label: string }) => (
);
type UsedByModalProps = {
values: string[];
id: string;
onClose: () => void;
isSpecificClient: boolean;
};
const UsedByModal = ({
values,
isSpecificClient,
onClose,
}: UsedByModalProps) => {
const UsedByModal = ({ id, isSpecificClient, onClose }: UsedByModalProps) => {
const { t } = useTranslation("authentication");
const { adminClient } = useAdminClient();
const loader = async (
first?: number,
max?: number,
search?: string
): Promise<{ name: string }[]> => {
const result = await fetchUsedBy({
adminClient,
id,
type: isSpecificClient ? "clients" : "idp",
first: first || 0,
max: max || 10,
search,
});
return result.map((p) => ({ name: p }));
};
return (
<Modal
header={
@ -66,7 +82,8 @@ const UsedByModal = ({
]}
>
<KeycloakDataTable
loader={values.map((value) => ({ name: value }))}
loader={loader}
isPaginated
ariaLabelKey="authentication:usedBy"
searchPlaceholderKey="common:search"
columns={[
@ -79,12 +96,7 @@ const UsedByModal = ({
);
};
export const UsedBy = ({
authType: {
id,
usedBy: { type, values },
},
}: UsedByProps) => {
export const UsedBy = ({ authType: { id, usedBy } }: UsedByProps) => {
const { t } = useTranslation("authentication");
const [open, toggle] = useToggle();
@ -92,26 +104,29 @@ export const UsedBy = ({
<>
{open && (
<UsedByModal
values={values}
id={id!}
onClose={toggle}
isSpecificClient={type === "specificClients"}
isSpecificClient={usedBy?.type === "SPECIFIC_CLIENTS"}
/>
)}
{(type === "specificProviders" || type === "specificClients") &&
(values.length < 8 ? (
{(usedBy?.type === "SPECIFIC_PROVIDERS" ||
usedBy?.type === "SPECIFIC_CLIENTS") &&
(usedBy.values.length <= 8 ? (
<Popover
key={id}
aria-label={t("usedBy")}
bodyContent={
<div key={`usedBy-${id}-${values}`}>
<div key={`usedBy-${id}-${usedBy.values}`}>
{t(
"appliedBy" +
(type === "specificClients" ? "Clients" : "Providers")
(usedBy.type === "SPECIFIC_CLIENTS"
? "Clients"
: "Providers")
)}{" "}
{values.map((used, index) => (
{usedBy.values.map((used, index) => (
<>
<strong>{used}</strong>
{index < values.length - 1 ? ", " : ""}
{index < usedBy.values.length - 1 ? ", " : ""}
</>
))}
</div>
@ -121,7 +136,7 @@ export const UsedBy = ({
variant="link"
className="keycloak__used-by__popover-button"
>
<Label label={t(type!)} />
<Label label={t(`used.${usedBy.type}`)} />
</Button>
</Popover>
) : (
@ -130,11 +145,19 @@ export const UsedBy = ({
className="keycloak__used-by__popover-button"
onClick={toggle}
>
<Label label={t(type!)} />
<Label label={t(`used.${usedBy.type}`)} />
</Button>
))}
{type === "default" && <Label label={t(`flow.${values[0]}`)} />}
{!type && t("notInUse")}
{usedBy?.type === "DEFAULT" && (
<Label
label={t(
[...REALM_FLOWS.values()].includes(usedBy.values[0])
? `flow.${usedBy.values[0]}`
: usedBy.values[0]
)}
/>
)}
{!usedBy?.type && t("used.notInUse")}
</>
);
};

View file

@ -1,21 +1,21 @@
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
import { fetchAdminUI } from "../../context/auth/admin-ui-endpoint";
type BaseClientRolesQuery = {
type BaseQuery = {
adminClient: KeycloakAdminClient;
id: string;
type: string;
};
type AvailableClientRolesQuery = BaseClientRolesQuery & {
type PaginatingQuery = BaseQuery & {
first: number;
max: number;
search?: string;
};
type EffectiveClientRolesQuery = BaseClientRolesQuery;
type EffectiveClientRolesQuery = BaseQuery;
type Query = Partial<Omit<AvailableClientRolesQuery, "adminClient">> & {
type Query = Partial<Omit<PaginatingQuery, "adminClient">> & {
adminClient: KeycloakAdminClient;
endpoint: string;
};
@ -28,7 +28,7 @@ type ClientRole = {
clientId: string;
};
const fetchRoles = async ({
const fetchEndpoint = async ({
adminClient,
id,
type,
@ -36,7 +36,7 @@ const fetchRoles = async ({
max,
search,
endpoint,
}: Query): Promise<ClientRole[]> => {
}: Query): Promise<any> => {
return fetchAdminUI(adminClient, `/admin-ui-${endpoint}/${type}/${id}`, {
first: (first || 0).toString(),
max: (max || 10).toString(),
@ -44,14 +44,15 @@ const fetchRoles = async ({
});
};
export const getAvailableClientRoles = async (
query: AvailableClientRolesQuery
): Promise<ClientRole[]> => {
return fetchRoles({ ...query, endpoint: "available-roles" });
};
export const getAvailableClientRoles = (
query: PaginatingQuery
): Promise<ClientRole[]> =>
fetchEndpoint({ ...query, endpoint: "available-roles" });
export const getEffectiveClientRoles = async (
export const getEffectiveClientRoles = (
query: EffectiveClientRolesQuery
): Promise<ClientRole[]> => {
return fetchRoles({ ...query, endpoint: "effective-roles" });
};
): Promise<ClientRole[]> =>
fetchEndpoint({ ...query, endpoint: "effective-roles" });
export const fetchUsedBy = (query: PaginatingQuery): Promise<string[]> =>
fetchEndpoint({ ...query, endpoint: "authentication-management" });

View file

@ -0,0 +1,33 @@
package org.keycloak.admin.ui.rest;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.services.resources.admin.AdminEventBuilder;
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider;
import org.keycloak.services.resources.admin.ext.AdminRealmResourceProviderFactory;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
public final class AuthenticationManagementProvider implements AdminRealmResourceProviderFactory, AdminRealmResourceProvider {
public AdminRealmResourceProvider create(KeycloakSession session) {
return this;
}
public void init(Config.Scope config) {
}
public void postInit(KeycloakSessionFactory factory) {
}
public void close() {
}
public String getId() {
return "admin-ui-authentication-management";
}
public Object getResource(KeycloakSession session, RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
return new AuthenticationManagementResource(realm, auth);
}
}

View file

@ -0,0 +1,115 @@
package org.keycloak.admin.ui.rest;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.keycloak.admin.ui.rest.model.Authentication;
import org.keycloak.admin.ui.rest.model.AuthenticationMapper;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
@Path("/")
@Consumes({"application/json"})
@Produces({"application/json"})
public class AuthenticationManagementResource extends RoleMappingResource {
@Context
private KeycloakSession session;
private RealmModel realm;
private AdminPermissionEvaluator auth;
public AuthenticationManagementResource(RealmModel realm, AdminPermissionEvaluator auth) {
super(realm, auth);
this.realm = realm;
this.auth = auth;
}
@GET
@Path("/flows")
@Operation(
summary = "List all authentication flows for this realm",
description = "This endpoint returns all the authentication flows and lists if there they are used."
)
@APIResponse(
responseCode = "200",
description = "",
content = {@Content(
schema = @Schema(
implementation = Authentication.class,
type = SchemaType.ARRAY
)
)}
)
public final List<Authentication> listIdentityProviders() {
auth.realm().requireViewAuthenticationFlows();
return realm.getAuthenticationFlowsStream()
.filter(flow -> flow.isTopLevel() && !Objects.equals(flow.getAlias(), DefaultAuthenticationFlows.SAML_ECP_FLOW))
.map(flow -> AuthenticationMapper.convertToModel(flow, realm))
.collect(Collectors.toList());
}
@GET
@Path("/{type}/{id}")
@Operation(
summary = "List all clients or identity providers that this flow is used by",
description = "List all the clients or identity providers this flow is used by as a paginated list"
)
@APIResponse(
responseCode = "200",
description = "",
content = {@Content(
schema = @Schema(
implementation = String.class,
type = SchemaType.ARRAY
)
)}
)
public final List<String> listUsed(@PathParam("id") String id, @PathParam("type") String type, @QueryParam("first") @DefaultValue("0") long first,
@QueryParam("max") @DefaultValue("10") long max, @QueryParam("search") @DefaultValue("") String search) {
auth.realm().requireViewAuthenticationFlows();
final AuthenticationFlowModel flow = realm.getAuthenticationFlowsStream().filter(f -> id.equals(f.getId())).collect(Collectors.toList()).get(0);
if ("clients".equals(type)) {
final Stream<ClientModel> clients = realm.getClientsStream();
return clients.filter(
c -> c.getAuthenticationFlowBindingOverrides().get("browser") != null && c.getAuthenticationFlowBindingOverrides()
.get("browser").equals(flow.getId()) || c.getAuthenticationFlowBindingOverrides()
.get("direct_grant") != null && c.getAuthenticationFlowBindingOverrides().get("direct_grant").equals(flow.getId()))
.map(ClientModel::getClientId).filter(f -> f.contains(search))
.skip("".equals(search) ? first : 0).limit(max).collect(Collectors.toList());
}
if ("idp".equals(type)) {
final Stream<IdentityProviderModel> identityProviders = realm.getIdentityProvidersStream();
return identityProviders.filter(idp -> idp.getFirstBrokerLoginFlowId().equals(flow.getId()))
.map(IdentityProviderModel::getAlias).filter(f -> f.contains(search))
.skip("".equals(search) ? first : 0).limit(max).collect(Collectors.toList());
}
throw new IllegalArgumentException("Invalid type");
}
}

View file

@ -1,6 +1,6 @@
package org.keycloak.admin.ui.rest;
import static org.keycloak.admin.ui.rest.model.RoleMapper.convertToRepresentation;
import static org.keycloak.admin.ui.rest.model.RoleMapper.convertToModel;
import java.util.List;
import java.util.function.Predicate;
@ -13,22 +13,21 @@ import org.keycloak.models.RoleModel;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
public abstract class RoleMappingResource {
private RealmModel realm;
private AdminPermissionEvaluator auth;
public final Stream<ClientRole> mapping(Predicate<RoleModel> predicate) {
return realm.getClientsStream().flatMap(RoleContainerModel::getRolesStream).filter(predicate)
.filter(auth.roles()::canMapClientScope).map(roleModel -> convertToRepresentation(roleModel, realm.getClientsStream()));
}
public final List<ClientRole> mapping(Predicate<RoleModel> predicate, long first, long max, final String search) {
return mapping(predicate).filter(clientRole -> clientRole.getClient().contains(search) || clientRole.getRole().contains(search))
.skip(first).limit(max).collect(Collectors.toList());
}
private final RealmModel realm;
private final AdminPermissionEvaluator auth;
public RoleMappingResource(RealmModel realm, AdminPermissionEvaluator auth) {
this.realm = realm;
this.auth = auth;
}
public final Stream<ClientRole> mapping(Predicate<RoleModel> predicate) {
return realm.getClientsStream().flatMap(RoleContainerModel::getRolesStream).filter(predicate)
.filter(auth.roles()::canMapClientScope).map(roleModel -> convertToModel(roleModel, realm.getClientsStream()));
}
public final List<ClientRole> mapping(Predicate<RoleModel> predicate, long first, long max, final String search) {
return mapping(predicate).filter(clientRole -> clientRole.getClient().contains(search) || clientRole.getRole().contains(search))
.skip(first).limit(max).collect(Collectors.toList());
}
}

View file

@ -0,0 +1,80 @@
package org.keycloak.admin.ui.rest.model;
import java.util.Objects;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
public class Authentication {
@Schema(required = true)
private String id;
@Schema(required = true)
private String alias;
@Schema(required = true)
private boolean builtIn;
private UsedBy usedBy;
private String description;
public UsedBy getUsedBy() {
return usedBy;
}
public void setUsedBy( UsedBy usedBy) {
this.usedBy = usedBy;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public boolean isBuiltIn() {
return builtIn;
}
public void setBuiltIn(boolean builtIn) {
this.builtIn = builtIn;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Authentication that = (Authentication) o;
return builtIn == that.builtIn && Objects.equals(usedBy, that.usedBy) && Objects.equals(id, that.id) && Objects.equals(alias,
that.alias) && Objects.equals(description, that.description);
}
@Override
public int hashCode() {
return Objects.hash(usedBy, id, builtIn, alias, description);
}
@Override public String toString() {
return "Authentication{" + "usedBy=" + usedBy + ", id='" + id + '\'' + ", buildIn=" + builtIn + ", alias='" + alias + '\'' + ", description='" + description + '\'' + '}';
}
}

View file

@ -0,0 +1,51 @@
package org.keycloak.admin.ui.rest.model;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
public class AuthenticationMapper {
private static final int MAX_USED_BY = 9;
public static Authentication convertToModel(AuthenticationFlowModel flow, RealmModel realm) {
final Stream<IdentityProviderModel> identityProviders = realm.getIdentityProvidersStream();
final Stream<ClientModel> clients = realm.getClientsStream();
final Authentication authentication = new Authentication();
authentication.setId(flow.getId());
authentication.setAlias(flow.getAlias());
authentication.setBuiltIn(flow.isBuiltIn());
authentication.setDescription(flow.getDescription());
final List<String> usedByIdp = identityProviders.filter(idp -> idp.getFirstBrokerLoginFlowId().equals(flow.getId()))
.map(IdentityProviderModel::getAlias).limit(MAX_USED_BY).collect(Collectors.toList());
if (!usedByIdp.isEmpty()) {
authentication.setUsedBy(new UsedBy(UsedBy.UsedByType.SPECIFIC_PROVIDERS, usedByIdp));
}
final List<String> usedClients = clients.filter(
c -> c.getAuthenticationFlowBindingOverrides().get("browser") != null && c.getAuthenticationFlowBindingOverrides()
.get("browser").equals(flow.getId()) || c.getAuthenticationFlowBindingOverrides()
.get("direct_grant") != null && c.getAuthenticationFlowBindingOverrides().get("direct_grant").equals(flow.getId()))
.map(ClientModel::getClientId).limit(MAX_USED_BY).collect(Collectors.toList());
if (!usedClients.isEmpty()) {
authentication.setUsedBy(new UsedBy(UsedBy.UsedByType.SPECIFIC_CLIENTS, usedClients));
}
final List<String> useAsDefault = Stream.of(realm.getBrowserFlow(), realm.getRegistrationFlow(), realm.getDirectGrantFlow(),
realm.getResetCredentialsFlow(), realm.getClientAuthenticationFlow(), realm.getDockerAuthenticationFlow())
.filter(f -> flow.getAlias().equals(f.getAlias())).map(AuthenticationFlowModel::getAlias).collect(Collectors.toList());
if (!useAsDefault.isEmpty()) {
authentication.setUsedBy(new UsedBy(UsedBy.UsedByType.DEFAULT, useAsDefault));
}
return authentication;
}
}

View file

@ -4,10 +4,14 @@ import java.util.Objects;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
public final class ClientRole {
@Schema(required = true) private final String id;
@Schema(required = true) private final String role;
@Schema(required = true) private String client;
@Schema(required = true) private String clientId;
@Schema(required = true)
private final String id;
@Schema(required = true)
private final String role;
@Schema(required = true)
private String client;
@Schema(required = true)
private String clientId;
private String description;
public String getId() {

View file

@ -6,7 +6,7 @@ import org.keycloak.models.RoleModel;
public class RoleMapper {
public static ClientRole convertToRepresentation(RoleModel roleModel, Stream<ClientModel> clients) {
public static ClientRole convertToModel(RoleModel roleModel, Stream<ClientModel> clients) {
ClientRole clientRole = new ClientRole(roleModel.getId(), roleModel.getName(), roleModel.getDescription());
ClientModel clientModel = clients.filter(c -> roleModel.getContainerId().equals(c.getId())).findFirst()
.orElseThrow(() -> new IllegalArgumentException("Could not find referenced client"));

View file

@ -0,0 +1,49 @@
package org.keycloak.admin.ui.rest.model;
import java.util.List;
import java.util.Objects;
public class UsedBy {
public UsedBy(UsedByType type, List<String> values) {
this.type = type;
this.values = values;
}
public enum UsedByType {
SPECIFIC_CLIENTS, SPECIFIC_PROVIDERS, DEFAULT
}
private UsedByType type;
private List<String> values;
public UsedByType getType() {
return type;
}
public void setType(UsedByType type) {
this.type = type;
}
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = values;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
UsedBy usedBy = (UsedBy) o;
return type == usedBy.type && Objects.equals(values, usedBy.values);
}
@Override
public int hashCode() {
return Objects.hash(type, values);
}
}

View file

@ -18,3 +18,4 @@
org.keycloak.admin.ui.rest.AvailableRoleMappingProvider
org.keycloak.admin.ui.rest.EffectiveRoleMappingProvider
org.keycloak.admin.ui.rest.GroupsResourceProvider
org.keycloak.admin.ui.rest.AuthenticationManagementProvider