8000 fix: adding defaults for boolean and several string values by shawkins · Pull Request #55168 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Draft
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
8000
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/documentation/upgrading/topics/changes/changes-27_0_0.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,40 @@ This prevents an attacker who controls a brokered account with an unverified ema

If you use auto-link flows with a SAML or custom identity provider that does not set the `email_verified` claim, enable *Trust Email* on that identity provider to preserve the previous behavior.

=== Admin API v2 applies client defaults for null values
// https://github.com/keycloak/keycloak/issues/49038

Admin API v2 client representations now define schema defaults for `enabled` and SAML settings, resolving omitted or `null` values to those defaults on create (`POST`) and replace (`PUT`).

On partial update (`PATCH`), omitted fields keep their current values, while fields explicitly set to `null` reset to their schema defaults.
Clients that serialize only non-null fields (such as the Java admin client) must send desired values explicitly when patching.

Previously, omitted or `null` fields on create or replace disabled the setting, left the client disabled, or removed the stored attribute.

The following fields now default to `true` when omitted or `null`:

* `enabled`
* `includeAuthnStatement`
* `signDocuments`
* `clientSignatureRequired`
* `forcePostBinding`
* `frontChannelLogout`

In addition, the following SAML fields resolve to defaults when omitted or `null`:

* `nameIdFormat`: `username`
* `signatureAlgorithm`: `RSA_SHA256`
* `signatureCanonicalizationMethod`: `http://www.w3.org/2001/10/xml-exc-c14n#`
* `forceNameIdFormat`, `signAssertions`, `allowEcpFlow`: `false`

To disable a setting or create a disabled client, specify `false` explicitly.
Review existing scripts or automation that relied on omitted or `null` fields to disable settings.

Other notable behavior changes:

* For existing SAML clients with no stored values for `includeAuthnStatement`, `signDocuments`, `clientSignatureRequired`, or `forcePostBinding`, the API now returns `false` (the effective runtime value) instead of `null`.
* Client policies, such as the secure logout executor, evaluate an unset `frontChannelLogout` field as `true`.

// ------------------------ Notable changes ------------------------ //
== Notable changes

Expand Down
11 changes: 11 additions & 0 deletions docs/guides/admin-api/admin-api-v2.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ The Admin API v2 uses the same CLI tool described in the {adminguide_link}#admin

<@kc.admin parameters="--v2 config credentials --server http://localhost:8080 --realm master --user admin"/>

== Update Semantics (PUT vs PATCH)

The Admin API v2 separates full resource replacement from partial updates:

* *Replace (`PUT`)*: Replaces the entire resource configuration with the representation provided in the request body.
`PUT` does not preserve previous state for omitted fields.
Any omitted field or field set to `null` is resolved to its default schema value (or removed if it has no default).
* *Partial Update (`PATCH`)*: Modifies only the fields explicitly provided in the request body.
Omitted fields retain their current persisted values.
Setting a field with a schema default explicitly to `null` resets it to its default value, while setting optional fields without a schema default to `null` removes the stored configuration.

== Available Resources

For details on filtering and projection, see <@links.adminapi id="querying" />.
Expand Down
14 changes: 14 additions & 0 deletions js/libs/keycloak-admin-client/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
},
"enabled" : {
"type" : "boolean",
"default" : true,
"description" : "Whether this client is enabled"
},
"appUrl" : {
Expand Down Expand Up @@ -166,43 +167,55 @@
"type" : "object",
"properties" : {
"nameIdFormat" : {
"type" : "string",
"default" : "username",
"$ref" : "#/components/schemas/NameIdFormat",
"description" : "Name ID format to use for the subject"
},
"forceNameIdFormat" : {
"type" : "boolean",
"default" : false,
"description" : "Force the specified Name ID format even if the client requests a different one"
},
"includeAuthnStatement" : {
"type" : "boolean",
"default" : true,
"description" : "Include AuthnStatement in the SAML response"
},
"signDocuments" : {
"type" : "boolean",
"default" : true,
"description" : "Sign SAML documents on the server side"
},
"signAssertions" : {
"type" : "boolean",
"default" : false,
"description" : "Sign SAML assertions"
},
"clientSignatureRequired" : {
"type" : "boolean",
"default" : true,
"description" : "Require client to sign SAML requests"
},
"forcePostBinding" : {
"type" : "boolean",
"default" : true,
"description" : "Force POST binding for SAML responses"
},
"frontChannelLogout" : {
"type" : "boolean",
"default" : true,
"description" : "Use front-channel logout (browser redirect)"
},
"signatureAlgorithm" : {
"type" : "string",
"default" : "RSA_SHA256",
"$ref" : "#/components/schemas/SignatureAlgorithm",
"description" : "Signature algorithm for signing SAML documents"
},
"signatureCanonicalizationMethod" : {
"type" : "string",
"default" : "http://www.w3.org/2001/10/xml-exc-c14n#",
"description" : "Canonicalization method for XML signatures. Validation: must be a valid XML canonicalization method URI (see javax.xml.crypto.dsig.CanonicalizationMethod constants)"
},
"signingCertificate" : {
Expand All @@ -212,6 +225,7 @@
},
"allowEcpFlow" : {
"type" : "boolean",
"default" : false,
"description" : "Allow ECP (Enhanced Client or Proxy) flow"
},
"protocol" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public void setDescription(String description) {
this.description = description;
}

@Schema(defaultValue = "true")
public Boolean getEnabled() {
return enabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public static SignatureAlgorithm fromJson(String value) {
@JsonPropertyDescription("Allow ECP (Enhanced Client or Proxy) flow")
private Boolean allowEcpFlow;

@Schema(defaultValue = "username")
Comment thread
shawkins marked this conversation as resolved.
Resolved
public NameIdFormat getNameIdFormat() {
return nameIdFormat;
}
Expand All @@ -109,6 +110,7 @@ public void setNameIdFormat(NameIdFormat nameIdFormat) {
this.nameIdFormat = nameIdFormat;
}

@Schema(defaultValue = "false")
public Boolean getForceNameIdFormat() {
return forceNameIdFormat;
}
Expand All @@ -117,6 +119,7 @@ public void setForceNameIdFormat(Boolean forceNameIdFormat) {
this.forceNameIdFormat = forceNameIdFormat;
}

@Schema(defaultValue = "true")
public Boolean getIncludeAuthnStatement() {
return includeAuthnStatement;
}
Expand All @@ -125,6 +128,7 @@ public void setIncludeAuthnStatement(Boolean includeAuthnStatement) {
this.includeAuthnStatement = includeAuthnStatement;
}

@Schema(defaultValue = "true")
Comment thread
shawkins marked this conversation as resolved.
Resolved
public Boolean getSignDocuments() {
return signDocuments;
}
Expand All @@ -133,6 +137,7 @@ public void setSignDocuments(Boolean signDocuments) {
this.signDocuments = signDocuments;
}

@Schema(defaultValue = "false")
public Boolean getSignAssertions() {
return signAssertions;
}
Expand All @@ -141,6 +146,7 @@ public void setSignAssertions(Boolean signAssertions) {
this.signAssertions = signAssertions;
}

@Schema(defaultValue = "true")
public Boolean getClientSignatureRequired() {
return clientSignatureRequired;
}
Expand All @@ -149,6 +155,7 @@ public void setClientSignatureRequired(Boolean clientSignatureRequired) {
this.clientSignatureRequired = clientSignatureRequired;
}

@Schema(defaultValue = "true")
public Boolean getForcePostBinding() {
return forcePostBinding;
}
Expand All @@ -157,6 +164,7 @@ public void setForcePostBinding(Boolean forcePostBinding) {
this.forcePostBinding = forcePostBinding;
}

@Schema(defaultValue = "true")
public Boolean getFrontChannelLogout() {
return frontChannelLogout;
}
Expand All @@ -165,6 +173,7 @@ public void setFrontChannelLogout(Boolean frontChannelLogout) {
this.frontChannelLogout = frontChannelLogout;
}

@Schema(defaultValue = "RSA_SHA256")
public SignatureAlgorithm getSignatureAlgorithm() {
return signatureAlgorithm;
}
Expand All @@ -173,6 +182,7 @@ public void setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
this.signatureAlgorithm = signatureAlgorithm;
}

@Schema(defaultValue = "http://www.w3.org/2001/10/xml-exc-c14n#")
public String getSignatureCanonicalizationMethod() {
return signatureCanonicalizationMethod;
}
Expand All @@ -189,6 +199,7 @@ public void setSigningCertificate(String signingCertificate) {
this.signingCertificate = signingCertificate;
}

@Schema(defaultValue = "false")
public Boolean getAllowEcpFlow() {
return allowEcpFlow;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected BaseClientModelSchema() {
map.put("protocol", stringAttr("protocol", "protocol", BaseClientRepresentation::setProtocol, ClientModel::setProtocol));
map.put("uuid", stringAttr("uuid", "id", BaseClientRepresentation::setUuid, null)); // read-only
map.put("clientId", stringAttr("clientId", "clientId", BaseClientRepresentation::setClientId, ClientModel::setClientId));
map.put("enabled", boolAttr ("enabled", "enabled", BaseClientRepresentation::setEnabled, (model, v) -> model.setEnabled(Boolean.TRUE.equals(v))));
map.put("enabled", boolAttr ("enabled", "enabled", BaseClientRepresentation::setEnabled, (model, v) -> model.setEnabled(!Boolean.FALSE.equals(v)))); // null is the default - true
map.put("description", stringAttr("description", "description", BaseClientRepresentation::setDescription, ClientModel::setDescription));
map.put("displayName", stringAttr("displayName", "name", BaseClientRepresentation::setDisplayName, ClientModel::setName));
map.put("appUrl", stringAttr("appUrl", "baseUrl", BaseClientRepresentation::setAppUrl, ClientModel::setBaseUrl));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,8 @@ private BaseClientRepresentation onCreateOrUpdate(BaseClientRepresentation clien
}
validator.validate(client, context, Default.class);
var proposedRepresentation = getProposedOldRepresentation(realm, client, schema);
if (client instanceof SAMLClientRepresentation samlClient) {
if (client instanceof SAMLClientRepresentation) {
proposedRepresentation.setStandardFlowEnabled(null);
proposedRepresentation.setFrontchannelLogout(samlClient.getFrontChannelLogout());
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import org.keycloak.models.ClientModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.admin.v2.OIDCClientRepresentation;
import org.keycloak.scim.resource.schema.attribute.Attribute;
import org.keycloak.scim.resource.schema.attribute.Attribute.ComplexAttributeOptions;
Expand Down Expand Up @@ -110,6 +111,8 @@ private void setAuth(ClientModel model, OIDCClientRepresentation.Auth auth) {
model.setSecret(auth.getSecret());
} else {
model.setPublicClient(true);
model.setClientAuthenticatorType(KeycloakModelUtils.getDefaultClientAuthenticatorType());
model.setSecret(null);
}
}

Expand Down
Loading
Loading
0