OIDCClientSecretConfigWrapper.java
package org.keycloak.protocol.oidc;
import java.io.InvalidObjectException;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSecretConstants;
import org.keycloak.models.delegate.ClientModelLazyDelegate;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.utils.StringUtil;
import static org.keycloak.models.ClientSecretConstants.CLIENT_ROTATED_SECRET;
import static org.keycloak.models.ClientSecretConstants.CLIENT_ROTATED_SECRET_CREATION_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_EXPIRATION;
import static org.keycloak.models.ClientSecretConstants.CLIENT_SECRET_REMAINING_EXPIRATION_TIME;
/**
* @author <a href="mailto:masales@redhat.com">Marcelo Sales</a>
*/
public class OIDCClientSecretConfigWrapper extends AbstractClientConfigWrapper {
private OIDCClientSecretConfigWrapper(ClientModel client, ClientRepresentation clientRep) {
super(client, clientRep);
}
public static OIDCClientSecretConfigWrapper fromClientModel(ClientModel client) {
return new OIDCClientSecretConfigWrapper(client, null);
}
public static OIDCClientSecretConfigWrapper fromClientRepresentation(ClientRepresentation clientRep) {
return new OIDCClientSecretConfigWrapper(null, clientRep);
}
public String getSecret() {
if (clientModel != null) {
return clientModel.getSecret();
} else {
return clientRep.getSecret();
}
}
public String getId() {
if (clientModel != null) {
return clientModel.getId();
} else {
return clientRep.getId();
}
}
public String getName() {
if (clientModel != null) {
return clientModel.getName();
} else {
return clientRep.getName();
}
}
public void removeClientSecretRotationInfo() {
setAttribute(CLIENT_SECRET_EXPIRATION, null);
setAttribute(CLIENT_SECRET_REMAINING_EXPIRATION_TIME, null);
removeClientSecretRotated();
}
public void removeClientSecretRotated() {
if (hasRotatedSecret()) {
setAttribute(CLIENT_ROTATED_SECRET, null);
setAttribute(CLIENT_ROTATED_SECRET_CREATION_TIME, null);
setAttribute(CLIENT_ROTATED_SECRET_EXPIRATION_TIME, null);
}
}
public int getClientSecretCreationTime() {
String creationTime = getAttribute(CLIENT_SECRET_CREATION_TIME);
return StringUtil.isBlank(creationTime) ? 0 : Integer.parseInt(creationTime);
}
public void setClientSecretCreationTime(int creationTime) {
setAttribute(CLIENT_SECRET_CREATION_TIME, String.valueOf(creationTime));
}
public boolean hasRotatedSecret() {
return StringUtil.isNotBlank(getAttribute(CLIENT_ROTATED_SECRET)) && StringUtil.isNotBlank(getAttribute(CLIENT_ROTATED_SECRET_CREATION_TIME));
}
public String getClientRotatedSecret() {
return getAttribute(CLIENT_ROTATED_SECRET);
}
public void setClientRotatedSecret(String secret) {
setAttribute(CLIENT_ROTATED_SECRET, secret);
}
public int getClientRotatedSecretCreationTime() {
String rotatedCreationTime = getAttribute(CLIENT_ROTATED_SECRET_CREATION_TIME);
if (StringUtil.isNotBlank(rotatedCreationTime)) return Integer.parseInt(rotatedCreationTime);
return 0;
}
public void setClientRotatedSecretCreationTime(Integer rotatedTime) {
setAttribute(CLIENT_ROTATED_SECRET_CREATION_TIME, rotatedTime != null ? String.valueOf(rotatedTime) : null);
}
/*
Update the creation time of a secret with current date time value
*/
public void setClientSecretCreationTime() {
setClientSecretCreationTime(Time.currentTime());
}
public void setClientRotatedSecretCreationTime() {
setClientRotatedSecretCreationTime(Time.currentTime());
}
public void updateClientRepresentationAttributes(ClientRepresentation rep) {
rep.getAttributes().put(CLIENT_ROTATED_SECRET, getAttribute(CLIENT_ROTATED_SECRET));
rep.getAttributes().put(CLIENT_SECRET_CREATION_TIME, getAttribute(CLIENT_SECRET_CREATION_TIME));
rep.getAttributes().put(CLIENT_SECRET_EXPIRATION, getAttribute(CLIENT_SECRET_EXPIRATION));
rep.getAttributes().put(CLIENT_ROTATED_SECRET_CREATION_TIME, getAttribute(CLIENT_ROTATED_SECRET_CREATION_TIME));
rep.getAttributes().put(CLIENT_ROTATED_SECRET_EXPIRATION_TIME, getAttribute(CLIENT_ROTATED_SECRET_EXPIRATION_TIME));
}
public boolean hasClientSecretExpirationTime() {
return getClientSecretExpirationTime() > 0;
}
public int getClientSecretExpirationTime() {
String expiration = getAttribute(CLIENT_SECRET_EXPIRATION);
return expiration == null ? 0 : Integer.parseInt(expiration);
}
public void setClientSecretExpirationTime(Integer expiration) {
setAttribute(ClientSecretConstants.CLIENT_SECRET_EXPIRATION, expiration != null ? String.valueOf(expiration) : null);
}
public boolean isClientSecretExpired() {
if (hasClientSecretExpirationTime()) {
return getClientSecretExpirationTime() < Time.currentTime();
}
return false;
}
public int getClientRotatedSecretExpirationTime() {
if (hasClientRotatedSecretExpirationTime()) {
return Integer.valueOf(getAttribute(ClientSecretConstants.CLIENT_ROTATED_SECRET_EXPIRATION_TIME));
}
return 0;
}
public void setClientRotatedSecretExpirationTime(Integer expiration) {
setAttribute(ClientSecretConstants.CLIENT_ROTATED_SECRET_EXPIRATION_TIME, expiration != null ? String.valueOf(expiration) : null);
}
public boolean hasClientRotatedSecretExpirationTime() {
return StringUtil.isNotBlank(getAttribute(ClientSecretConstants.CLIENT_ROTATED_SECRET_EXPIRATION_TIME));
}
public boolean isClientRotatedSecretExpired() {
if (hasClientRotatedSecretExpirationTime()) {
return getClientRotatedSecretExpirationTime() < Time.currentTime();
}
return true;
}
//validates the rotated secret (value and expiration)
public boolean validateRotatedSecret(String secret) {
// there must exist a rotated_secret
if (hasRotatedSecret()) {
// the rotated secret must not be outdated
if (isClientRotatedSecretExpired()) {
return false;
}
} else {
return false;
}
return MessageDigest.isEqual(secret.getBytes(), getClientRotatedSecret().getBytes());
}
public String toJson() {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = new HashMap<>();
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
map.put("clientId", getId());
map.put("clientName", getName());
map.put("secretCreationTimeSeconds", getClientSecretCreationTime());
map.put("secretCreationTime", sdf.format(Time.toDate(getClientSecretCreationTime())));
map.put("secretExpirationTimeSeconds", getClientSecretExpirationTime());
map.put("secretExpirationTime", sdf.format(Time.toDate(getClientSecretExpirationTime())));
map.put("rotatedSecretCreationTimeSeconds", getClientRotatedSecretCreationTime());
map.put("rotatedSecretCreationTime", sdf.format(Time.toDate(getClientRotatedSecretCreationTime())));
map.put("rotatedSecretExpirationTimeSeconds", getClientRotatedSecretExpirationTime());
map.put("rotatedSecretExpirationTime", sdf.format(Time.toDate(getClientRotatedSecretExpirationTime())));
return mapper.writeValueAsString(map);
} catch (JsonProcessingException e) {
return "";
}
}
public ReadOnlyRotatedSecretClientModel toRotatedClientModel() throws InvalidObjectException {
if (Objects.isNull(this.clientModel))
throw new InvalidObjectException(getClass().getCanonicalName() + " does not have an attribute of type " + ClientModel.class.getCanonicalName());
return new ReadOnlyRotatedSecretClientModel(clientModel);
}
/**
* Representation of a client model that passes information from a rotated secret. The goal is to act as a decorator/DTO just providing information and not updating objects persistently.
*/
public class ReadOnlyRotatedSecretClientModel extends ClientModelLazyDelegate {
private ReadOnlyRotatedSecretClientModel(ClientModel clientModel) {
super(() -> clientModel);
}
@Override
public String getSecret() {
return OIDCClientSecretConfigWrapper.this.getClientRotatedSecret();
}
}
}