Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion js/apps/admin-ui/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -3291,5 +3291,13 @@
"scopeTypeHelp": "Client scopes, which will be added as default scopes to each created client",
"clientDescriptionHelp": "Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example: ${my_client_description}",
"clientsClientTypeHelp": "'OpenID Connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information.",
"clientsClientScopesHelp": "The scopes associated with this resource."
"clientsClientScopesHelp": "The scopes associated with this resource.",
"openIdEndpointConfiguration": "OpenID Endpoint Configuration",
"claimsSupportedHelp": "Claims supported by this realm. Values will be shown in claims_supported",
"claimsSupported": "Claims supported",
"submit": "Submit",
"addClaimButton": "Add new Supported Claim Button",
"newClaimInput": "New Supported Claim Input",
"removeClaimButton":"Remove Supported Claim Button",
"editClaimInput" : "Edit Supported Claim Input"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import {
ActionGroup,
Button,
FormGroup,
PageSection,
InputGroup,
TextInput,
} from "@patternfly/react-core";
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormAccess } from "../components/form/FormAccess";
import { HelpItem } from "ui-shared";
import { MinusIcon, PlusIcon } from "@patternfly/react-icons";

type RealmSettingsThemesTabProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void;
};

export const OpenIdEndpointConfigurationTab = ({
realm,
save,
}: RealmSettingsThemesTabProps) => {
const { t } = useTranslation();

const [newClaim, setNewClaim] = useState("");
const { control, handleSubmit, setValue } = useForm<RealmRepresentation>();

const setupForm = () => {
const attributes = realm.attributes || {};
setValue("attributes", attributes);
};

useEffect(setupForm, []);

return (
<PageSection variant="light">
<FormAccess
isHorizontal
role="manage-realm"
className="pf-u-mt-lg"
onSubmit={handleSubmit(save)}
>
<FormGroup
label={t("claimsSupported")}
fieldId="kc-claims-supported"
labelIcon={
<HelpItem
helpText={t("claimsSupportedHelp")}
fieldLabelId="claimsSupported"
/>
}
>
<Controller
name="attributes"
control={control}
render={({ field }) => {
const claimsSupported = field.value?.claimsSupported?.trim()
? field.value.claimsSupported.split(",")
: [];
return (
<>
{claimsSupported
.filter(Boolean)
.map((claim: string, index: number) => (
<InputGroup key={index}>
<TextInput
value={claim}
onChange={(value) => {
const attributes = field.value || {};
claimsSupported[index] = value;
attributes.claimsSupported =
claimsSupported.join(",");
field.onChange(attributes);
}}
aria-label={t("editClaimInput")}
/>
<Button
icon={<MinusIcon />}
aria-label={t("removeClaimButton")}
onClick={() => {
const attributes = field.value || {};
claimsSupported.splice(index, 1);
attributes.claimsSupported =
claimsSupported.join(",");
field.onChange(attributes);
}}
variant="control"
/>
</InputGroup>
))}
<InputGroup>
<TextInput
value={newClaim}
onChange={(value) => {
setNewClaim(value);
}}
aria-label={t("newClaimInput")}
/>
<Button
icon={<PlusIcon />}
aria-label={t("addClaimButton")}
onClick={() => {
if (newClaim) {
const attributes = field.value || {};
claimsSupported.push(newClaim);
attributes.claimsSupported =
claimsSupported.join(",");
setNewClaim("");
field.onChange(attributes);
}
}}
variant="control"
/>
</InputGroup>
</>
);
}}
/>
</FormGroup>

<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="openid-configuration-tab-save"
aria-label={t("submit")}
>
{t("save")}
</Button>
<Button variant="link" onClick={setupForm}>
{t("revert")}
</Button>
</ActionGroup>
</FormAccess>
</PageSection>
);
};
13 changes: 13 additions & 0 deletions js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { ClientPoliciesTab, toClientPolicies } from "./routes/ClientPolicies";
import { RealmSettingsTab, toRealmSettings } from "./routes/RealmSettings";
import { SecurityDefenses } from "./security-defences/SecurityDefenses";
import { UserProfileTab } from "./user-profile/UserProfileTab";
import { OpenIdEndpointConfigurationTab } from "./OpenIdEndpointConfigurationTab";

type RealmSettingsHeaderProps = {
onChange: (value: boolean) => void;
Expand Down Expand Up @@ -239,6 +240,9 @@ export const RealmSettingsTabs = ({
const clientPoliciesTab = useTab("client-policies");
const userProfileTab = useTab("user-profile");
const userRegistrationTab = useTab("user-registration");
const openIdEndpointConfigurationTab = useTab(
"openid-endpoint-configuration",
);

const useClientPoliciesTab = (tab: ClientPoliciesTab) =>
useRoutableTab(
Expand Down Expand Up @@ -345,6 +349,15 @@ export const RealmSettingsTabs = ({
>
<RealmSettingsSessionsTab key={key} realm={realm} save={save} />
</Tab>
<Tab
title={
<TabTitleText>{t("openIdEndpointConfiguration")}</TabTitleText>
}
data-testid="rs-openid-tab"
{...openIdEndpointConfigurationTab}
>
<OpenIdEndpointConfigurationTab realm={realm} save={save} />
</Tab>
<Tab
title={<TabTitleText>{t("tokens")}</TabTitleText>}
data-testid="rs-tokens-tab"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export type RealmSettingsTab =
| "tokens"
| "client-policies"
| "user-profile"
| "user-registration";
| "user-registration"
| "openid-endpoint-configuration";

export type RealmSettingsParams = {
realm: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,18 @@ public void setActionTokenGeneratedByUserLifespan(String actionTokenId, Integer
}
}

@Override
public List<String> getClaimsSupported() {
if (isUpdated()) return updated.getClaimsSupported();
return cached.getClaimsSupported();
}

@Override
public void setClaimsSupported(List<String> claimsSupported) {
getDelegateForUpdate();
updated.setClaimsSupported(claimsSupported);
}

@Override
public Stream<RequiredCredentialModel> getRequiredCredentialsStream() {
if (isUpdated()) return updated.getRequiredCredentialsStream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ public Set<IdentityProviderMapperModel> getIdentityProviderMapperSet() {

protected Map<String, Map<String,String>> realmLocalizationTexts;

protected List<String> claimsSupported;

public CachedRealm(Long revision, RealmModel model) {
super(revision, model.getId());
name = model.getName();
Expand Down Expand Up @@ -239,6 +241,7 @@ public CachedRealm(Long revision, RealmModel model) {

requiredCredentials = model.getRequiredCredentialsStream().collect(Collectors.toList());
userActionTokenLifespans = Collections.unmodifiableMap(new HashMap<>(model.getUserActionTokenLifespans()));
claimsSupported = model.getClaimsSupported();

this.identityProviders = model.getIdentityProvidersStream().map(IdentityProviderModel::new)
.collect(Collectors.toList());
Expand Down Expand Up @@ -743,4 +746,8 @@ public boolean isAllowUserManagedAccess() {
public Map<String, Map<String, String>> getRealmLocalizationTexts() {
return realmLocalizationTexts;
}

public List<String> getClaimsSupported() {
return claimsSupported;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import jakarta.persistence.TypedQuery;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Objects.nonNull;
Expand Down Expand Up @@ -629,6 +630,20 @@ public void setActionTokenGeneratedByUserLifespan(String actionTokenId, Integer
setAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN + "." + actionTokenId, actionTokenGeneratedByUserLifespan);
}

@Override
public List<String> getClaimsSupported() {
if (getAttribute(RealmAttributes.CLAIMS_SUPPORTED) != null) {
return new ArrayList<String>(Arrays.asList(getAttribute(RealmAttributes.CLAIMS_SUPPORTED).split(",")));
} else {
return null;
}
}

@Override
public void setClaimsSupported(List<String> claimsSupported) {
setAttribute(RealmAttributes.CLAIMS_SUPPORTED, claimsSupported.stream().collect(Collectors.joining(",")));
}

protected RequiredCredentialModel initRequiredCredentialModel(String type) {
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
if (model == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ public interface RealmAttributes {

String ADMIN_EVENTS_EXPIRATION = "adminEventsExpiration";

String CLAIMS_SUPPORTED = "claimsSupported";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.keycloak.migration.migrators;

import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.RealmRepresentation;

import java.util.Arrays;
import java.util.List;

public class MigrateTo23_0_0 implements Migration {

public static final ModelVersion VERSION = new ModelVersion("23.0.0");
private static final List<String> DEFAULT_CLAIMS_SUPPORTED = Arrays.asList("aud", "sub", "iss", IDToken.AUTH_TIME, IDToken.NAME, IDToken.GIVEN_NAME, IDToken.FAMILY_NAME, IDToken.PREFERRED_USERNAME, IDToken.EMAIL, IDToken.ACR);

@Override
public void migrate(KeycloakSession session) {
session.realms().getRealmsStream().forEach(realm -> realm.setClaimsSupported(DEFAULT_CLAIMS_SUPPORTED));
}

@Override
public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) {
realm.setClaimsSupported(DEFAULT_CLAIMS_SUPPORTED);
}


@Override
public ModelVersion getVersion() {
return VERSION;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.keycloak.models.map.realm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -86,6 +88,7 @@ public class MapRealmAdapter extends AbstractRealmModel<MapRealmEntity> implemen
private static final String MINIMUM_QUICK_LOGIN_WAIT_SECONDS = "minimumQuickLoginWaitSeconds";
private static final String MAX_DELTA_SECONDS = "maxDeltaTimeSeconds";
private static final String FAILURE_FACTOR = "failureFactor";
private static final String CLAIMS_SUPPORTED = "claimsSupported";

private PasswordPolicy passwordPolicy;

Expand Down Expand Up @@ -1716,6 +1719,20 @@ public void setBrowserSecurityHeaders(Map<String, String> headers) {
entity.setBrowserSecurityHeaders(headers);
}

@Override
public List<String> getClaimsSupported() {
if (getAttribute(CLAIMS_SUPPORTED) != null) {
return new ArrayList<String>(Arrays.asList(getAttribute(CLAIMS_SUPPORTED).split(",")));
} else {
return null;
}
}

@Override
public void setClaimsSupported(List<String> claimsSupported) {
setAttribute(CLAIMS_SUPPORTED, claimsSupported.stream().collect(Collectors.joining(",")));
}

@Override
public ClientInitialAccessModel createClientInitialAccessModel(int expiration, int count) {
MapClientInitialAccessEntity clientInitialAccess = MapClientInitialAccessEntity.createEntity(expiration, count);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.keycloak.models.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
Expand Down Expand Up @@ -1747,6 +1748,16 @@ public Stream<ClientInitialAccessModel> getClientInitialAccesses() {
public void decreaseRemainingCount(ClientInitialAccessModel clientInitialAccess) {

}

@Override
public List<String> getClaimsSupported() {
return null;
}

@Override
public void setClaimsSupported(List<String> claimsSupported) {

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.keycloak.provider.ProviderEvent;
import org.keycloak.storage.SearchableModelField;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
Expand Down Expand Up @@ -239,6 +240,10 @@ default Boolean getAttribute(String name, Boolean defaultValue) {

void setAccessCodeLifespanUserAction(int seconds);

List<String> getClaimsSupported();

void setClaimsSupported(List<String> claimsSupported);

OAuth2DeviceConfig getOAuth2DeviceConfig();

CibaConfig getCibaPolicy();
Expand Down
Loading