Update secret rotation when the policy is disabled (#10674)
Closes #10667
This commit is contained in:
parent
1a4d7c297a
commit
0c25da542c
4 changed files with 103 additions and 5 deletions
|
@ -18,6 +18,7 @@ import static org.keycloak.models.ClientSecretConstants.CLIENT_ROTATED_SECRET_CR
|
||||||
import static org.keycloak.models.ClientSecretConstants.CLIENT_ROTATED_SECRET_EXPIRATION_TIME;
|
import static org.keycloak.models.ClientSecretConstants.CLIENT_ROTATED_SECRET_EXPIRATION_TIME;
|
||||||
import static org.keycloak.models.ClientSecretConstants.CLIENT_SECRET_CREATION_TIME;
|
import static org.keycloak.models.ClientSecretConstants.CLIENT_SECRET_CREATION_TIME;
|
||||||
import static org.keycloak.models.ClientSecretConstants.CLIENT_SECRET_EXPIRATION;
|
import static org.keycloak.models.ClientSecretConstants.CLIENT_SECRET_EXPIRATION;
|
||||||
|
import static org.keycloak.models.ClientSecretConstants.CLIENT_SECRET_REMAINING_EXPIRATION_TIME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:masales@redhat.com">Marcelo Sales</a>
|
* @author <a href="mailto:masales@redhat.com">Marcelo Sales</a>
|
||||||
|
@ -61,6 +62,12 @@ public class OIDCClientSecretConfigWrapper extends AbstractClientConfigWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeClientSecretRotationInfo() {
|
||||||
|
setAttribute(CLIENT_SECRET_EXPIRATION, null);
|
||||||
|
setAttribute(CLIENT_SECRET_REMAINING_EXPIRATION_TIME, null);
|
||||||
|
removeClientSecretRotated();
|
||||||
|
}
|
||||||
|
|
||||||
public void removeClientSecretRotated() {
|
public void removeClientSecretRotated() {
|
||||||
if (hasRotatedSecret()) {
|
if (hasRotatedSecret()) {
|
||||||
setAttribute(CLIENT_ROTATED_SECRET, null);
|
setAttribute(CLIENT_ROTATED_SECRET, null);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientSecretConstants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCClientSecretConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCClientSecretConfigWrapper;
|
||||||
|
@ -48,7 +49,7 @@ public class ClientSecretRotationExecutor implements
|
||||||
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
|
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
|
||||||
if (!session.getContext().getClient().isPublicClient() && !session.getContext().getClient()
|
if (!session.getContext().getClient().isPublicClient() && !session.getContext().getClient()
|
||||||
.isBearerOnly()) {
|
.isBearerOnly()) {
|
||||||
|
session.setAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED,Boolean.TRUE);
|
||||||
switch (context.getEvent()) {
|
switch (context.getEvent()) {
|
||||||
case REGISTERED:
|
case REGISTERED:
|
||||||
case UPDATED:
|
case UPDATED:
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
|
import org.keycloak.models.ClientSecretConstants;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
@ -141,6 +142,7 @@ public class ClientResource {
|
||||||
auth.clients().requireConfigure(client);
|
auth.clients().requireConfigure(client);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
session.setAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED,Boolean.FALSE);
|
||||||
session.clientPolicy().triggerOnEvent(new AdminClientUpdateContext(rep, client, auth.adminAuth()));
|
session.clientPolicy().triggerOnEvent(new AdminClientUpdateContext(rep, client, auth.adminAuth()));
|
||||||
|
|
||||||
updateClientFromRep(rep, client, session);
|
updateClientFromRep(rep, client, session);
|
||||||
|
@ -155,6 +157,12 @@ public class ClientResource {
|
||||||
|
|
||||||
session.clientPolicy().triggerOnEvent(new AdminClientUpdatedContext(rep, client, auth.adminAuth()));
|
session.clientPolicy().triggerOnEvent(new AdminClientUpdatedContext(rep, client, auth.adminAuth()));
|
||||||
|
|
||||||
|
if (!(boolean) session.getAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED)){
|
||||||
|
logger.debugv("Removing the previous rotation info for client {0}{1}, if there is",client.getClientId(),client.getName());
|
||||||
|
OIDCClientSecretConfigWrapper.fromClientModel(client).removeClientSecretRotationInfo();
|
||||||
|
}
|
||||||
|
session.removeAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED);
|
||||||
|
|
||||||
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(rep).success();
|
adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(rep).success();
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
|
@ -253,6 +261,7 @@ public class ClientResource {
|
||||||
auth.clients().requireConfigure(client);
|
auth.clients().requireConfigure(client);
|
||||||
|
|
||||||
logger.debug("regenerateSecret");
|
logger.debug("regenerateSecret");
|
||||||
|
session.setAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED,Boolean.FALSE);
|
||||||
|
|
||||||
ClientRepresentation representation = ModelToRepresentation.toRepresentation(client, session);
|
ClientRepresentation representation = ModelToRepresentation.toRepresentation(client, session);
|
||||||
ClientSecretRotationContext secretRotationContext = new ClientSecretRotationContext(
|
ClientSecretRotationContext secretRotationContext = new ClientSecretRotationContext(
|
||||||
|
@ -265,10 +274,14 @@ public class ClientResource {
|
||||||
CredentialRepresentation rep = new CredentialRepresentation();
|
CredentialRepresentation rep = new CredentialRepresentation();
|
||||||
rep.setType(CredentialRepresentation.SECRET);
|
rep.setType(CredentialRepresentation.SECRET);
|
||||||
rep.setValue(secret);
|
rep.setValue(secret);
|
||||||
rep.setCreatedDate(
|
|
||||||
(long) OIDCClientSecretConfigWrapper.fromClientModel(client).getClientSecretCreationTime());
|
if (!(boolean) session.getAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED)){
|
||||||
|
logger.debugv("Removing the previous rotation info for client {0}{1}, if there is",client.getClientId(),client.getName());
|
||||||
|
OIDCClientSecretConfigWrapper.fromClientModel(client).removeClientSecretRotationInfo();
|
||||||
|
}
|
||||||
|
|
||||||
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(rep).success();
|
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(rep).success();
|
||||||
|
session.removeAttribute(ClientSecretConstants.CLIENT_SECRET_ROTATION_ENABLED);
|
||||||
|
|
||||||
return rep;
|
return rep;
|
||||||
} catch (ClientPolicyException cpe) {
|
} catch (ClientPolicyException cpe) {
|
||||||
|
|
|
@ -615,12 +615,78 @@ public class ClientSecretRotationTest extends AbstractRestServiceTest {
|
||||||
try {
|
try {
|
||||||
configureCustomProfileAndPolicy(60, 61, 30);
|
configureCustomProfileAndPolicy(60, 61, 30);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
assertThat(e,instanceOf(ClientPolicyException.class));
|
assertThat(e, instanceOf(ClientPolicyException.class));
|
||||||
}
|
}
|
||||||
// no police must have been created due to the above error
|
// no police must have been created due to the above error
|
||||||
ClientPoliciesPoliciesResource policiesResource = adminClient.realm(REALM_NAME).clientPoliciesPoliciesResource();
|
ClientPoliciesPoliciesResource policiesResource = adminClient.realm(REALM_NAME).clientPoliciesPoliciesResource();
|
||||||
ClientPoliciesRepresentation policies = policiesResource.getPolicies();
|
ClientPoliciesRepresentation policies = policiesResource.getPolicies();
|
||||||
assertThat(policies.getPolicies(),is(empty()));
|
assertThat(policies.getPolicies(), is(empty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When there is a client that has a secret rotated and the policy is disabled, Rotation information must be removed after updating a client
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void removingPolicyMustClearRotationInformationFromClientOnUpdate() throws Exception {
|
||||||
|
//create and enable the profile
|
||||||
|
configureDefaultProfileAndPolicy();
|
||||||
|
//create client
|
||||||
|
String cidConfidential = createClientByAdmin(DEFAULT_CLIENT_ID);
|
||||||
|
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(cidConfidential);
|
||||||
|
String firstSecret = clientResource.getSecret().getValue();
|
||||||
|
String newSecret = clientResource.generateNewSecret().getValue();
|
||||||
|
String rotatedSecret = clientResource.getClientRotatedSecret().getValue();
|
||||||
|
assertThat(firstSecret, not(equalTo(newSecret)));
|
||||||
|
assertThat(firstSecret, equalTo(rotatedSecret));
|
||||||
|
|
||||||
|
//disable the profile
|
||||||
|
disableProfile();
|
||||||
|
|
||||||
|
//force a update
|
||||||
|
ClientRepresentation clientRepresentation = clientResource.toRepresentation();
|
||||||
|
clientRepresentation.setDescription("New Description Updated");
|
||||||
|
clientResource.update(clientRepresentation);
|
||||||
|
|
||||||
|
//client must not have any information about rotation in it
|
||||||
|
clientRepresentation = clientResource.toRepresentation();
|
||||||
|
OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientRepresentation(clientRepresentation);
|
||||||
|
|
||||||
|
assertThat(wrapper.hasRotatedSecret(), is(false));
|
||||||
|
assertThat(wrapper.getClientSecretExpirationTime(),is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When there is a client that has a secret rotated and the policy is disabled, Rotation information must be removed after request a new secret
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void removingPolicyMustClearRotationInformationFromClientOnRequestNewSecret() throws Exception {
|
||||||
|
//create and enable the profile
|
||||||
|
configureDefaultProfileAndPolicy();
|
||||||
|
//create client
|
||||||
|
String cidConfidential = createClientByAdmin(DEFAULT_CLIENT_ID);
|
||||||
|
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(cidConfidential);
|
||||||
|
String firstSecret = clientResource.getSecret().getValue();
|
||||||
|
String newSecret = clientResource.generateNewSecret().getValue();
|
||||||
|
String rotatedSecret = clientResource.getClientRotatedSecret().getValue();
|
||||||
|
assertThat(firstSecret, not(equalTo(newSecret)));
|
||||||
|
assertThat(firstSecret, equalTo(rotatedSecret));
|
||||||
|
|
||||||
|
//disable the profile
|
||||||
|
disableProfile();
|
||||||
|
|
||||||
|
//Request a new secret
|
||||||
|
newSecret = clientResource.generateNewSecret().getValue();
|
||||||
|
|
||||||
|
//client must not have any information about rotation in it
|
||||||
|
ClientRepresentation clientRepresentation = clientResource.toRepresentation();
|
||||||
|
OIDCClientSecretConfigWrapper wrapper = OIDCClientSecretConfigWrapper.fromClientRepresentation(clientRepresentation);
|
||||||
|
assertThat(clientResource.getSecret().getValue(),equalTo(newSecret));
|
||||||
|
assertThat(wrapper.hasRotatedSecret(), is(false));
|
||||||
|
assertThat(wrapper.getClientSecretExpirationTime(),is(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -646,6 +712,17 @@ public class ClientSecretRotationTest extends AbstractRestServiceTest {
|
||||||
doConfigProfileAndPolicy(profileBuilder, profileConfig);
|
doConfigProfileAndPolicy(profileBuilder, profileConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void disableProfile() throws Exception {
|
||||||
|
Configuration config = new Configuration();
|
||||||
|
config.setType(Arrays.asList(ClientAccessTypeConditionFactory.TYPE_CONFIDENTIAL));
|
||||||
|
String json = (new ClientPoliciesBuilder()).addPolicy(
|
||||||
|
(new ClientPolicyBuilder()).createPolicy(POLICY_NAME,
|
||||||
|
"Policy for Client Secret Rotation",
|
||||||
|
Boolean.FALSE).addCondition(ClientAccessTypeConditionFactory.PROVIDER_ID, config)
|
||||||
|
.addProfile(PROFILE_NAME).toRepresentation()).toString();
|
||||||
|
updatePolicies(json);
|
||||||
|
}
|
||||||
|
|
||||||
private void doConfigProfileAndPolicy(ClientProfileBuilder profileBuilder,
|
private void doConfigProfileAndPolicy(ClientProfileBuilder profileBuilder,
|
||||||
ClientSecretRotationExecutor.Configuration profileConfig) throws Exception {
|
ClientSecretRotationExecutor.Configuration profileConfig) throws Exception {
|
||||||
String json = (new ClientProfilesBuilder()).addProfile(
|
String json = (new ClientProfilesBuilder()).addProfile(
|
||||||
|
|
Loading…
Reference in a new issue