Skip to content

Commit 741c0ad

Browse files
authored
[OID4VCI] Expose advanced realm-level OID4VCI settings in the Admin UI (keycloak#44615)
closes keycloak#43900 Signed-off-by: Ogenbertrand <[email protected]>
1 parent 2f7045d commit 741c0ad

File tree

3 files changed

+433
-4
lines changed

3 files changed

+433
-4
lines changed

js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3602,10 +3602,38 @@ request_uri_not_supported=Request uri not supported
36023602
registration_not_supported=Registration not supported
36033603
oid4vciAttributes=OID4VCI attributes
36043604
oid4vciNonceLifetime=OID4VCI Nonce Lifetime
3605-
oid4vciNonceLifetimeHelp=The lifetime of the OID4VCI nonce in seconds.
3605+
oid4vciNonceLifetimeHelp=The lifetime of the OID4VCI nonce.
36063606
preAuthorizedCodeLifespan=Pre-Authorized Code Lifespan
3607-
preAuthorizedCodeLifespanHelp=The lifespan of the pre-authorized code in seconds.
3607+
preAuthorizedCodeLifespanHelp=The lifespan of the pre-authorized code.
36083608
oid4vciFormValidationError=Please ensure the OID4VCI attribute fields are filled with values 30 seconds or greater.
3609+
signedIssuerMetadata=Signed Issuer Metadata
3610+
signedIssuerMetadataHelp=Enable signing of the issuer metadata. When enabled, the issuer metadata will be signed using the configured signing algorithm.
3611+
signedMetadataLifespan=Signed Metadata Lifespan
3612+
signedMetadataLifespanHelp=The lifetime of the signed metadata. After this time, the signed metadata will expire.
3613+
signedMetadataSigningAlgorithm=Signed Metadata Signing Algorithm
3614+
signedMetadataSigningAlgorithmHelp=The algorithm used to sign the issuer metadata. This ensures the integrity and authenticity of the metadata.
3615+
requireEncryption=Require Encryption
3616+
requireEncryptionHelp=If enabled, encryption is required for credential requests. Clients must encrypt their requests using the supported encryption algorithms.
3617+
enableDeflateCompression=Enable DEF compression
3618+
enableDeflateCompressionHelp=If enabled, the DEF compression algorithm is supported for credential requests. This allows clients to compress their requests to reduce payload size.
3619+
batchIssuanceSize=Batch Issuance Size
3620+
batchIssuanceSizeHelp=The maximum number of credentials that can be issued in a single batch request. This helps manage server load and response times.
3621+
timeClaimCorrelationMitigation=Time-claim correlation mitigation
3622+
timeClaimsStrategy=Strategy to apply to time claims
3623+
timeClaimsStrategyHelp=Strategy to apply to time claims. Supported values: off, randomize, round.
3624+
randomizeWindow=Randomize Window
3625+
randomizeWindowHelp=When strategy is randomize, subtract a random number of seconds between 0 and the value of this attribute from the original timestamp to mitigate correlation attacks.
3626+
roundUnit=Round Unit
3627+
roundUnitHelp=When strategy is round, truncate timestamps to the selected unit boundary (UTC). Supported values: SECOND, MINUTE, HOUR, DAY.
3628+
randomize=Randomize
3629+
round=Round
3630+
second=Second
3631+
day=Day
3632+
attestationTrust=Attestation Trust
3633+
trustedKeyIds=Trusted Key IDs
3634+
trustedKeyIdsHelp=Comma-separated list of Key IDs (kid) from the realm keystore. These keys are trusted for validating wallet attestations.
3635+
trustedKeys=Trusted Keys (JSON)
3636+
trustedKeysHelp=A JSON array of JWK objects. These keys are trusted for validating wallet attestations in addition to realm keys.
36093637
# OID4VCI Credential Configuration
36103638
credentialConfigurationId=Credential Configuration ID
36113639
credentialConfigurationIdHelp=The unique identifier for this credential configuration. This ID is used in the credential issuer metadata and credential requests.

js/apps/admin-ui/src/realm-settings/TokensTab.tsx

Lines changed: 216 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
SelectVariant,
66
ScrollForm,
77
useAlerts,
8+
SelectControl,
9+
NumberControl,
810
} from "@keycloak/keycloak-ui-shared";
911
import {
1012
AlertVariant,
@@ -17,13 +19,15 @@ import {
1719
Switch,
1820
Text,
1921
TextInput,
22+
TextArea,
2023
TextVariants,
2124
} from "@patternfly/react-core";
2225
import { useState } from "react";
2326
import { Controller, useFormContext, useWatch } from "react-hook-form";
2427
import { useTranslation } from "react-i18next";
2528
import { FormAccess } from "../components/form/FormAccess";
2629
import { FixedButtonsGroup } from "../components/form/FixedButtonGroup";
30+
import { DefaultSwitchControl } from "../components/SwitchControl";
2731
import { convertAttributeNameToForm } from "../util";
2832
import {
2933
TimeSelector,
@@ -37,15 +41,15 @@ import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
3741

3842
import "./realm-settings-section.css";
3943

40-
type RealmSettingsSessionsTabProps = {
44+
type RealmSettingsTokensTabProps = {
4145
realm: RealmRepresentation;
4246
save: (realm: RealmRepresentation) => void;
4347
};
4448

4549
export const RealmSettingsTokensTab = ({
4650
realm,
4751
save,
48-
}: RealmSettingsSessionsTabProps) => {
52+
}: RealmSettingsTokensTabProps) => {
4953
const { t } = useTranslation();
5054
const { addAlert } = useAlerts();
5155
const serverInfo = useServerInfo();
@@ -59,6 +63,9 @@ export const RealmSettingsTokensTab = ({
5963
serverInfo.providers!["signature"].providers,
6064
);
6165

66+
const asymmetricSigAlgOptions =
67+
serverInfo.cryptoInfo?.clientSignatureAsymmetricAlgorithms ?? [];
68+
6269
const { control, register, reset, formState, handleSubmit } =
6370
useFormContext<RealmRepresentation>();
6471

@@ -85,6 +92,26 @@ export const RealmSettingsTokensTab = ({
8592
defaultValue: false,
8693
});
8794

95+
const signedMetadataEnabled = useWatch({
96+
control,
97+
name: convertAttributeNameToForm(
98+
"attributes.oid4vci.signed_metadata.enabled",
99+
),
100+
defaultValue: realm.attributes?.["oid4vci.signed_metadata.enabled"],
101+
});
102+
103+
const encryptionRequired = useWatch({
104+
control,
105+
name: convertAttributeNameToForm("attributes.oid4vci.encryption.required"),
106+
defaultValue: realm.attributes?.["oid4vci.encryption.required"],
107+
});
108+
109+
const strategy = useWatch({
110+
control,
111+
name: convertAttributeNameToForm("attributes.oid4vci.time.claims.strategy"),
112+
defaultValue: realm.attributes?.["oid4vci.time.claims.strategy"] ?? "off",
113+
});
114+
88115
const sections = [
89116
{
90117
title: t("general"),
@@ -674,6 +701,193 @@ export const RealmSettingsTokensTab = ({
674701
min={30}
675702
units={["second", "minute", "hour"]}
676703
/>
704+
<DefaultSwitchControl
705+
name={convertAttributeNameToForm(
706+
"attributes.oid4vci.signed_metadata.enabled",
707+
)}
708+
label={t("signedIssuerMetadata")}
709+
labelIcon={t("signedIssuerMetadataHelp")}
710+
stringify
711+
data-testid="signed-metadata-switch"
712+
/>
713+
{signedMetadataEnabled === "true" && (
714+
<>
715+
<TimeSelectorControl
716+
name={convertAttributeNameToForm(
717+
"attributes.oid4vci.signed_metadata.lifespan",
718+
)}
719+
label={t("signedMetadataLifespan")}
720+
labelIcon={t("signedMetadataLifespanHelp")}
721+
controller={{
722+
defaultValue: 60,
723+
}}
724+
units={["second", "minute", "hour"]}
725+
data-testid="signed-metadata-lifespan"
726+
/>
727+
<SelectControl
728+
name={convertAttributeNameToForm(
729+
"attributes.oid4vci.signed_metadata.alg",
730+
)}
731+
label={t("signedMetadataSigningAlgorithm")}
732+
labelIcon={t("signedMetadataSigningAlgorithmHelp")}
733+
controller={{
734+
defaultValue: "RS256",
735+
}}
736+
options={asymmetricSigAlgOptions.map((p) => ({
737+
key: p,
738+
value: p,
739+
}))}
740+
data-testid="signed-metadata-signing-algorithm"
741+
/>
742+
</>
743+
)}
744+
<DefaultSwitchControl
745+
name={convertAttributeNameToForm(
746+
"attributes.oid4vci.encryption.required",
747+
)}
748+
label={t("requireEncryption")}
749+
labelIcon={t("requireEncryptionHelp")}
750+
stringify
751+
data-testid="require-encryption-switch"
752+
/>
753+
{encryptionRequired === "true" && (
754+
<DefaultSwitchControl
755+
name={convertAttributeNameToForm(
756+
"attributes.oid4vci.request.zip.algorithms",
757+
)}
758+
label={t("enableDeflateCompression")}
759+
labelIcon={t("enableDeflateCompressionHelp")}
760+
data-testid="deflate-compression-switch"
761+
stringify
762+
/>
763+
)}
764+
<NumberControl
765+
name={convertAttributeNameToForm(
766+
"attributes.oid4vci.batch_credential_issuance.batch_size",
767+
)}
768+
label={t("batchIssuanceSize")}
769+
labelIcon={t("batchIssuanceSizeHelp")}
770+
min={2}
771+
controller={{
772+
defaultValue: 2,
773+
rules: { min: 2 },
774+
}}
775+
data-testid="batch-issuance-size"
776+
/>
777+
778+
<Text
779+
className="kc-override-action-tokens-subtitle"
780+
component={TextVariants.h1}
781+
>
782+
{t("attestationTrust")}
783+
</Text>
784+
<FormGroup
785+
label={t("trustedKeyIds")}
786+
fieldId="trustedKeyIds"
787+
labelIcon={
788+
<HelpItem
789+
helpText={t("trustedKeyIdsHelp")}
790+
fieldLabelId="trustedKeyIds"
791+
/>
792+
}
793+
>
794+
<TextInput
795+
id="trustedKeyIds"
796+
data-testid="trusted-key-ids"
797+
{...register(
798+
convertAttributeNameToForm(
799+
"attributes.oid4vc.attestation.trusted_key_ids",
800+
),
801+
)}
802+
/>
803+
</FormGroup>
804+
<FormGroup
805+
label={t("trustedKeys")}
806+
fieldId="trustedKeys"
807+
labelIcon={
808+
<HelpItem
809+
helpText={t("trustedKeysHelp")}
810+
fieldLabelId="trustedKeys"
811+
/>
812+
}
813+
>
814+
<Controller
815+
name={convertAttributeNameToForm(
816+
"attributes.oid4vc.attestation.trusted_keys",
817+
)}
818+
control={control}
819+
defaultValue={
820+
realm.attributes?.["oid4vc.attestation.trusted_keys"]
821+
}
822+
render={({ field }) => (
823+
<TextArea
824+
id="trustedKeys"
825+
data-testid="trusted-keys"
826+
value={field.value}
827+
onChange={(_event, value) => field.onChange(value)}
828+
resizeOrientation="vertical"
829+
/>
830+
)}
831+
/>
832+
</FormGroup>
833+
834+
<Text
835+
className="kc-override-action-tokens-subtitle"
836+
component={TextVariants.h1}
837+
>
838+
{t("timeClaimCorrelationMitigation")}
839+
</Text>
840+
<SelectControl
841+
name={convertAttributeNameToForm(
842+
"attributes.oid4vci.time.claims.strategy",
843+
)}
844+
label={t("timeClaimsStrategy")}
845+
labelIcon={t("timeClaimsStrategyHelp")}
846+
controller={{
847+
defaultValue: "off",
848+
}}
849+
options={[
850+
{ key: "off", value: t("off") },
851+
{ key: "randomize", value: t("randomize") },
852+
{ key: "round", value: t("round") },
853+
]}
854+
data-testid="time-claims-strategy"
855+
/>
856+
{strategy === "randomize" && (
857+
<NumberControl
858+
name={convertAttributeNameToForm(
859+
"attributes.oid4vci.time.randomize.window.seconds",
860+
)}
861+
label={t("randomizeWindow")}
862+
labelIcon={t("randomizeWindowHelp")}
863+
min={1}
864+
controller={{
865+
defaultValue: 86400,
866+
rules: { min: 1 },
867+
}}
868+
data-testid="randomize-window"
869+
widthChars={6}
870+
/>
871+
)}
872+
{strategy === "round" && (
873+
<SelectControl
874+
name={convertAttributeNameToForm(
875+
"attributes.oid4vci.time.round.unit",
876+
)}
877+
label={t("roundUnit")}
878+
labelIcon={t("roundUnitHelp")}
879+
controller={{
880+
defaultValue: "SECOND",
881+
}}
882+
options={[
883+
{ key: "SECOND", value: t("times.seconds") },
884+
{ key: "MINUTE", value: t("times.minutes") },
885+
{ key: "HOUR", value: t("times.hours") },
886+
{ key: "DAY", value: t("times.days") },
887+
]}
888+
data-testid="round-unit"
889+
/>
890+
)}
677891
<FixedButtonsGroup
678892
name="tokens-tab"
679893
isSubmit

0 commit comments

Comments
 (0)