Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@
import org.keycloak.models.mapper.ClientModelMappers;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
import org.keycloak.representations.admin.v2.OIDCClientRepresentation;
import org.keycloak.representations.admin.v2.SAMLClientRepresentation;
import org.keycloak.representations.admin.v2.validation.CreateClient;
import org.keycloak.representations.admin.v2.validation.PatchClient;
import org.keycloak.representations.admin.v2.validation.PutClient;
Expand Down Expand Up @@ -339,6 +342,10 @@ private CreateOrUpdateResult createOrUpdate(RealmModel realm, String clientId, B
}
validator.validate(client, strategy.getValidationGroup(), Default.class);
var proposedRepresentation = getProposedOldRepresentation(realm, client, mapper);
if (client instanceof SAMLClientRepresentation samlClient) {
proposedRepresentation.setStandardFlowEnabled(null);
proposedRepresentation.setFrontchannelLogout(samlClient.getFrontChannelLogout());
}
session.clientPolicy().triggerOnEvent(new AdminClientRegisterContext(proposedRepresentation, permissions.adminAuth()));

// Add basic attributes
Expand All @@ -348,6 +355,7 @@ private CreateOrUpdateResult createOrUpdate(RealmModel realm, String clientId, B
// Generate random secret if applicable
generateClientSecretIfNeeded(client, model, strategy, patchExplicitNullSecret);
mapper.toModel(client, model);
setupClientDefaults(client, model, proposedRepresentation);
Comment thread
arnabnandy7 marked this conversation as resolved.

// Validate the fully populated model
ValidationUtil.validateClient(session, model, true, r -> {
Expand All @@ -373,6 +381,22 @@ private CreateOrUpdateResult createOrUpdate(RealmModel realm, String clientId, B
return new CreateOrUpdateResult(mapper.fromModel(model), !alreadyExists);
}

private void setupClientDefaults(BaseClientRepresentation client, ClientModel model, ClientRepresentation proposedRepresentation) {
LoginProtocolFactory factory = (LoginProtocolFactory) session.getKeycloakSessionFactory()
.getProviderFactory(LoginProtocol.class, client.getProtocol());
if (factory != null) {
factory.setupClientDefaults(proposedRepresentation, model);
}
if (client instanceof OIDCClientRepresentation oidcClient
&& oidcClient.getAuth() != null
&& !isClientSecret(oidcClient.getAuth().getMethod())
&& isBlank(oidcClient.getAuth().getSecret())) {
// OIDCLoginProtocolFactory generates a secret for every confidential client, while Admin API v2

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but you do this even when factory == null, how should I understand this comment then?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with leaving it as is, but it's certainly fine to move this block into the preceding if (factory != null) block.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not a bug, I I wasn't certain that it can't have side effects which brought me to reading the comment. At least the comment is wrong, but I am fine with it as is.

// only uses secrets with secret-based authentication methods.
model.setSecret(null);
}
}

/**
* Fires a v2 admin event for client operations (only enabled for testing now to avoid duplicated admin events)
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,24 @@

import org.junit.jupiter.api.Test;

import static org.keycloak.protocol.oidc.OIDCConfigAttributes.BACKCHANNEL_LOGOUT_SESSION_REQUIRED;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_ALLOW_ECP_FLOW;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_ARTIFACT_BINDING_IDENTIFIER;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_ASSERTION_SIGNATURE;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_AUTHNSTATEMENT;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_CANONICALIZATION_METHOD_ATTRIBUTE;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_FORCE_POST_BINDING;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_SERVER_SIGNATURE;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM;
import static org.keycloak.protocol.saml.SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
Expand Down Expand Up @@ -154,6 +160,24 @@ public void createWithV2AssertWithV1() {

}

@Test
public void createMinimalOidcWithV2AppliesProtocolDefaults() {
RealmResource realm = adminClient.realm(getRealmName());
OIDCClientRepresentation client = new OIDCClientRepresentation("v2-minimal-oidc-client");

try (var response = getClientsApi().createClient(client)) {
assertThat(response.getStatus(), is(201));
}

ClientRepresentation created = realm.clients().findByClientId(client.getClientId()).get(0);
try {
created = realm.clients().get(created.getId()).toRepresentation();
assertThat(created.getAttributes().get(BACKCHANNEL_LOGOUT_SESSION_REQUIRED), is("true"));
} finally {
realm.clients().get(created.getId()).remove();
}
}

/**
* Test: Create a client using v1 API, update it using v2 API, then assert using v1 API.
*/
Expand Down Expand Up @@ -304,6 +328,37 @@ public void createSamlWithV2AssertWithV1() {
}
}

@Test
public void createMinimalSamlWithV2AppliesProtocolDefaults() {
RealmResource realm = adminClient.realm(getRealmName());
SAMLClientRepresentation client = new SAMLClientRepresentation();
client.setClientId("v2-minimal-saml-client");

try (var response = getClientsApi().createClient(client)) {
assertThat(response.getStatus(), is(201));
}

ClientRepresentation created = realm.clients().findByClientId(client.getClientId()).get(0);
try {
created = realm.clients().get(created.getId()).toRepresentation();
assertThat(created.isStandardFlowEnabled(), is(true));
assertThat(created.isFrontchannelLogout(), is(true));
assertThat(created.getAttributes().get(SAML_NAME_ID_FORMAT_ATTRIBUTE), is("username"));
assertThat(created.getAttributes().get(SAML_AUTHNSTATEMENT), is("true"));
assertThat(created.getAttributes().get(SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE), is("false"));
assertThat(created.getAttributes().get(SAML_SERVER_SIGNATURE), is("true"));
assertThat(created.getAttributes().get(SAML_CLIENT_SIGNATURE_ATTRIBUTE), is("true"));
assertThat(created.getAttributes().get(SAML_FORCE_POST_BINDING), is("true"));
assertThat(created.getAttributes().get(SAML_SIGNATURE_ALGORITHM), is("RSA_SHA256"));
assertThat(created.getAttributes().get(SAML_CANONICALIZATION_METHOD_ATTRIBUTE), is("http://www.w3.org/2001/10/xml-exc-c14n#"));
assertThat(created.getAttributes().get(SAML_ALLOW_ECP_FLOW), is("false"));
assertThat(created.getAttributes().get(SAML_SIGNING_CERTIFICATE_ATTRIBUTE), notNullValue());
assertThat(created.getAttributes().get(SAML_ARTIFACT_BINDING_IDENTIFIER), notNullValue());
} finally {
realm.clients().get(created.getId()).remove();
}
}

/**
* Test: Create a SAML client using v1 API, update it using v2 API, then assert using v1 API.
*/
Expand Down
Loading