Refactored StripSecretsUtils in order to make it unit-testable, added unit tests for it

Don't mask secrets at realm export

Closes #21562

Signed-off-by: Joerg Matysiak <joerg.matysiak@bosch.com>
This commit is contained in:
Joerg Matysiak 2024-01-18 14:54:41 +01:00 committed by Pedro Igor
parent 7483bae130
commit 76a5a27082
13 changed files with 359 additions and 147 deletions

View file

@ -62,7 +62,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
import static org.keycloak.models.utils.StripSecretsUtils.stripSecrets;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -256,7 +255,7 @@ public class ExportUtils {
// Message Bundle // Message Bundle
rep.setLocalizationTexts(realm.getRealmLocalizationTexts()); rep.setLocalizationTexts(realm.getRealmLocalizationTexts());
return stripSecrets(session, rep); return rep;
} }
public static MultivaluedHashMap<String, ComponentExportRepresentation> exportComponents(RealmModel realm, String parentId) { public static MultivaluedHashMap<String, ComponentExportRepresentation> exportComponents(RealmModel realm, String parentId) {

View file

@ -123,6 +123,7 @@ import static org.keycloak.models.utils.RepresentationToModel.createGroups;
import static org.keycloak.models.utils.RepresentationToModel.createRoleMappings; import static org.keycloak.models.utils.RepresentationToModel.createRoleMappings;
import static org.keycloak.models.utils.RepresentationToModel.importGroup; import static org.keycloak.models.utils.RepresentationToModel.importGroup;
import static org.keycloak.models.utils.RepresentationToModel.importRoles; import static org.keycloak.models.utils.RepresentationToModel.importRoles;
import static org.keycloak.models.utils.StripSecretsUtils.stripSecrets;
/** /**
* This wraps the functionality about export/import for the storage. * This wraps the functionality about export/import for the storage.
@ -142,6 +143,7 @@ public class DefaultExportImportManager implements ExportImportManager {
callback.setType(MediaType.APPLICATION_JSON); callback.setType(MediaType.APPLICATION_JSON);
callback.writeToOutputStream(outputStream -> { callback.writeToOutputStream(outputStream -> {
RealmRepresentation rep = ExportUtils.exportRealm(session, realm, options, false); RealmRepresentation rep = ExportUtils.exportRealm(session, realm, options, false);
stripSecrets(session, rep);
JsonSerialization.writeValueToStream(outputStream, rep); JsonSerialization.writeValueToStream(outputStream, rep);
outputStream.close(); outputStream.close();
}); });

View file

@ -498,7 +498,7 @@ public class ModelToRepresentation {
} }
List<IdentityProviderRepresentation> identityProviders = realm.getIdentityProvidersStream() List<IdentityProviderRepresentation> identityProviders = realm.getIdentityProvidersStream()
.map(provider -> toRepresentation(session, realm, provider)).collect(Collectors.toList()); .map(provider -> toRepresentation(realm, provider)).collect(Collectors.toList());
rep.setIdentityProviders(identityProviders); rep.setIdentityProviders(identityProviders);
List<IdentityProviderMapperRepresentation> identityProviderMappers = realm.getIdentityProviderMappersStream() List<IdentityProviderMapperRepresentation> identityProviderMappers = realm.getIdentityProviderMappersStream()
@ -784,7 +784,7 @@ public class ModelToRepresentation {
return providerRep; return providerRep;
} }
public static IdentityProviderRepresentation toRepresentation(KeycloakSession session, RealmModel realm, IdentityProviderModel identityProviderModel) { public static IdentityProviderRepresentation toRepresentation(RealmModel realm, IdentityProviderModel identityProviderModel) {
IdentityProviderRepresentation providerRep = toBriefRepresentation(realm, identityProviderModel); IdentityProviderRepresentation providerRep = toBriefRepresentation(realm, identityProviderModel);
providerRep.setLinkOnly(identityProviderModel.isLinkOnly()); providerRep.setLinkOnly(identityProviderModel.isLinkOnly());
@ -818,7 +818,7 @@ public class ModelToRepresentation {
providerRep.setPostBrokerLoginFlowAlias(flow.getAlias()); providerRep.setPostBrokerLoginFlowAlias(flow.getAlias());
} }
return stripSecrets(session, providerRep); return providerRep;
} }
public static ProtocolMapperRepresentation toRepresentation(ProtocolMapperModel model) { public static ProtocolMapperRepresentation toRepresentation(ProtocolMapperModel model) {

View file

@ -33,6 +33,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -46,6 +47,12 @@ public class StripSecretsUtils {
private static final Map<Class<?>, BiConsumer<KeycloakSession, Object>> REPRESENTATION_FORMATTER = new HashMap<>(); private static final Map<Class<?>, BiConsumer<KeycloakSession, Object>> REPRESENTATION_FORMATTER = new HashMap<>();
/** interface to encapsulate the getComponentProperties() function in order to make the code unit-testable
*/
protected interface GetComponentPropertiesFn {
Map<String, ProviderConfigProperty> getComponentProperties(KeycloakSession session, String providerType, String providerId);
}
static { static {
REPRESENTATION_FORMATTER.put(RealmRepresentation.class, (session, o) -> StripSecretsUtils.stripRealm(session, (RealmRepresentation) o)); REPRESENTATION_FORMATTER.put(RealmRepresentation.class, (session, o) -> StripSecretsUtils.stripRealm(session, (RealmRepresentation) o));
REPRESENTATION_FORMATTER.put(UserRepresentation.class, (session, o) -> StripSecretsUtils.stripUser((UserRepresentation) o)); REPRESENTATION_FORMATTER.put(UserRepresentation.class, (session, o) -> StripSecretsUtils.stripUser((UserRepresentation) o));
@ -77,11 +84,18 @@ public class StripSecretsUtils {
private static ComponentRepresentation stripComponent(KeycloakSession session, ComponentRepresentation rep) { private static ComponentRepresentation stripComponent(KeycloakSession session, ComponentRepresentation rep) {
Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, rep); Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, rep);
if (rep.getConfig() == null) { return stripComponent(configProperties, rep);
return rep; }
}
Iterator<Map.Entry<String, List<String>>> itr = rep.getConfig().entrySet().iterator(); protected static ComponentRepresentation stripComponent( Map<String, ProviderConfigProperty> configProperties, ComponentRepresentation rep) {
if (rep.getConfig() != null) {
stripComponentConfigMap(rep.getConfig(), configProperties);
}
return rep;
}
private static void stripComponentConfigMap(MultivaluedHashMap<String, String> configMap, Map<String, ProviderConfigProperty> configProperties) {
Iterator<Map.Entry<String, List<String>>> itr = configMap.entrySet().iterator();
while (itr.hasNext()) { while (itr.hasNext()) {
Map.Entry<String, List<String>> next = itr.next(); Map.Entry<String, List<String>> next = itr.next();
ProviderConfigProperty configProperty = configProperties.get(next.getKey()); ProviderConfigProperty configProperty = configProperties.get(next.getKey());
@ -97,110 +111,76 @@ public class StripSecretsUtils {
itr.remove(); itr.remove();
} }
} }
}
private static Map<String, String> stripFromMap(Map<String, String> map, String key) {
if ((map != null) && map.containsKey(key)) {
map.put(key, maskNonVaultValue(map.get(key)));
}
return map;
}
protected static IdentityProviderRepresentation stripBroker(IdentityProviderRepresentation rep) {
stripFromMap(rep.getConfig(), "clientSecret");
return rep; return rep;
} }
private static RealmRepresentation stripRealm(RealmRepresentation rep) { private static RealmRepresentation stripRealm(RealmRepresentation rep) {
if (rep.getSmtpServer() != null && rep.getSmtpServer().containsKey("password")) { stripFromMap(rep.getSmtpServer(), "password");
rep.getSmtpServer().put("password", maskNonVaultValue(rep.getSmtpServer().get("password")));
}
return rep;
}
private static IdentityProviderRepresentation stripBroker(IdentityProviderRepresentation rep) {
if (rep.getConfig() != null && rep.getConfig().containsKey("clientSecret")) {
rep.getConfig().put("clientSecret", maskNonVaultValue(rep.getConfig().get("clientSecret")));
}
return rep; return rep;
} }
private static void stripRealm(KeycloakSession session, RealmRepresentation rep) { private static void stripRealm(KeycloakSession session, RealmRepresentation rep) {
stripRealm(session, rep, ComponentUtil::getComponentConfigProperties);
}
protected static void stripRealm(KeycloakSession session, RealmRepresentation rep, GetComponentPropertiesFn fnGetConfigProperties) {
stripRealm(rep); stripRealm(rep);
List<ClientRepresentation> clients = rep.getClients(); Optional.ofNullable(rep.getClients())
if (clients != null) { .ifPresent(clients -> clients.forEach(StripSecretsUtils::stripClient));
for (ClientRepresentation c : clients) {
stripClient(c);
}
}
List<IdentityProviderRepresentation> providers = rep.getIdentityProviders();
if (providers != null) {
for (IdentityProviderRepresentation r : providers) {
stripBroker(r);
}
}
MultivaluedHashMap<String, ComponentExportRepresentation> components = rep.getComponents(); Optional.ofNullable(rep.getIdentityProviders())
if (components != null) { .ifPresent(providers -> providers.forEach(StripSecretsUtils::stripBroker));
for (Map.Entry<String, List<ComponentExportRepresentation>> ent : components.entrySet()) {
for (ComponentExportRepresentation c : ent.getValue()) {
stripComponentExport(session, ent.getKey(), c);
}
}
}
List<UserRepresentation> users = rep.getUsers(); Optional.ofNullable(rep.getComponents())
if (users != null) { .ifPresent(components -> components
for (UserRepresentation u: users) { .forEach((providerType, componentList)-> componentList
stripUser(u); .forEach(component -> stripComponentExport(session, providerType, component, fnGetConfigProperties))));
}
}
users = rep.getFederatedUsers(); Optional.ofNullable(rep.getUsers())
if (users != null) { .ifPresent(users -> users.forEach(StripSecretsUtils::stripUser));
for (UserRepresentation u: users) {
stripUser(u); Optional.ofNullable(rep.getFederatedUsers())
} .ifPresent(users -> users.forEach(StripSecretsUtils::stripUser));
}
} }
private static UserRepresentation stripUser(UserRepresentation user) { protected static UserRepresentation stripUser(UserRepresentation user) {
user.setCredentials(null); user.setCredentials(null);
return user; return user;
} }
private static ClientRepresentation stripClient(ClientRepresentation rep) { protected static ClientRepresentation stripClient(ClientRepresentation rep) {
if (rep.getSecret() != null) { if (rep.getSecret() != null) {
rep.setSecret(maskNonVaultValue(rep.getSecret())); rep.setSecret(maskNonVaultValue(rep.getSecret()));
} }
if (rep.getAttributes() != null && rep.getAttributes().containsKey(ClientSecretConstants.CLIENT_ROTATED_SECRET)) {
rep.getAttributes().put( stripFromMap(rep.getAttributes(), ClientSecretConstants.CLIENT_ROTATED_SECRET);
ClientSecretConstants.CLIENT_ROTATED_SECRET,
maskNonVaultValue(rep.getAttributes().get(ClientSecretConstants.CLIENT_ROTATED_SECRET))
);
}
return rep; return rep;
} }
private static ComponentExportRepresentation stripComponentExport(KeycloakSession session, String providerType, ComponentExportRepresentation rep) { private static ComponentExportRepresentation stripComponentExport(KeycloakSession session, String providerType, ComponentExportRepresentation rep) {
Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, providerType, rep.getProviderId()); return stripComponentExport(session, providerType, rep, ComponentUtil::getComponentConfigProperties);
if (rep.getConfig() == null) { }
return rep; private static ComponentExportRepresentation stripComponentExport(KeycloakSession session, String providerType, ComponentExportRepresentation rep, GetComponentPropertiesFn fnGetConfigProperties) {
Map<String, ProviderConfigProperty> configProperties = fnGetConfigProperties.getComponentProperties(session, providerType, rep.getProviderId());
if (rep.getConfig() != null) {
stripComponentConfigMap(rep.getConfig(), configProperties);
} }
Iterator<Map.Entry<String, List<String>>> itr = rep.getConfig().entrySet().iterator(); rep.getSubComponents()
while (itr.hasNext()) { .forEach((subCompProviderType, subCompProviders) ->
Map.Entry<String, List<String>> next = itr.next(); subCompProviders.forEach(subComp -> stripComponentExport(session, subCompProviderType, subComp)));
ProviderConfigProperty configProperty = configProperties.get(next.getKey());
if (configProperty != null) {
if (configProperty.isSecret()) {
if (next.getValue() == null || next.getValue().isEmpty()) {
next.setValue(Collections.singletonList(ComponentRepresentation.SECRET_VALUE));
} else {
next.setValue(next.getValue().stream().map(StripSecretsUtils::maskNonVaultValue).collect(Collectors.toList()));
}
}
} else {
itr.remove();
}
}
MultivaluedHashMap<String, ComponentExportRepresentation> sub = rep.getSubComponents();
for (Map.Entry<String, List<ComponentExportRepresentation>> ent: sub.entrySet()) {
for (ComponentExportRepresentation c: ent.getValue()) {
stripComponentExport(session, ent.getKey(), c);
}
}
return rep; return rep;
} }

View file

@ -1,57 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models;
import org.junit.Test;
import org.keycloak.models.utils.StripSecretsUtils;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
public class StripSecretsUtilsTest {
@Test
public void checkStrippedRotatedSecret() {
ClientRepresentation stripped = StripSecretsUtils.stripSecrets(null, createClient("unmasked_secret"));
assertEquals(ComponentRepresentation.SECRET_VALUE, getRotatedSecret(stripped));
}
@Test
public void checkStrippedRotatedSecretVaultUnaffected() {
String rotatedSecret = "${vault.key}";
ClientRepresentation stripped = StripSecretsUtils.stripSecrets(null, createClient(rotatedSecret));
assertEquals(rotatedSecret, getRotatedSecret(stripped));
}
private ClientRepresentation createClient(String rotatedSecret) {
ClientRepresentation client = new ClientRepresentation();
Map<String, String> attrs = new HashMap<>();
attrs.put(ClientSecretConstants.CLIENT_ROTATED_SECRET, rotatedSecret);
client.setAttributes(attrs);
return client;
}
private String getRotatedSecret(ClientRepresentation clientRepresentation) {
return clientRepresentation.getAttributes().get(ClientSecretConstants.CLIENT_ROTATED_SECRET);
}
}

View file

@ -0,0 +1,266 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.utils;
import org.junit.Test;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.ClientSecretConstants;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.*;
public class StripSecretsUtilsTest {
@Test
public void checkStrippedRotatedSecret() {
ClientRepresentation stripped = StripSecretsUtils.stripSecrets(null, createClient("unmasked_secret"));
assertEquals(ComponentRepresentation.SECRET_VALUE, getRotatedSecret(stripped));
}
@Test
public void checkStrippedRotatedSecretVaultUnaffected() {
String rotatedSecret = "${vault.key}";
ClientRepresentation stripped = StripSecretsUtils.stripSecrets(null, createClient(rotatedSecret));
assertEquals(rotatedSecret, getRotatedSecret(stripped));
}
private ClientRepresentation createClient(String rotatedSecret) {
ClientRepresentation client = new ClientRepresentation();
Map<String, String> attrs = new HashMap<>();
attrs.put(ClientSecretConstants.CLIENT_ROTATED_SECRET, rotatedSecret);
client.setAttributes(attrs);
return client;
}
private String getRotatedSecret(ClientRepresentation clientRepresentation) {
return clientRepresentation.getAttributes().get(ClientSecretConstants.CLIENT_ROTATED_SECRET);
}
@Test
public void stripUser() {
UserRepresentation rep = new UserRepresentation();
rep.setId("userId");
CredentialRepresentation credentialRepresentation = new CredentialRepresentation();
credentialRepresentation.setType("password");
credentialRepresentation.setSecretData("myPassword");
rep.setCredentials(Arrays.asList(credentialRepresentation));
rep.setEnabled(true);
StripSecretsUtils.stripUser(rep);
assertEquals("userId", rep.getId());
assertNull(rep.getCredentials());
}
@Test
public void stripClient() {
ClientRepresentation rep = new ClientRepresentation();
rep.setId("clientId");
rep.setSecret("clientSecret");
rep.setAttributes(new HashMap<>());
rep.getAttributes().put("clientAttr1", "clientAttr1Value");
rep.getAttributes().put("client.secret.rotated", "rotatedSecret");
StripSecretsUtils.stripClient(rep);
assertEquals("clientId", rep.getId());
assertEquals("**********", rep.getSecret());
assertEquals(2, rep.getAttributes().size());
assertEquals("clientAttr1Value", rep.getAttributes().get("clientAttr1"));
assertEquals("**********", rep.getAttributes().get("client.secret.rotated"));
}
@Test
public void stripClientSecretsFromVault() {
ClientRepresentation rep = new ClientRepresentation();
rep.setId("clientId");
rep.setSecret("${vault.clientSecret}");
rep.setAttributes(new HashMap<>());
rep.getAttributes().put("clientAttr1", "clientAttr1Value");
rep.getAttributes().put("client.secret.rotated", "${vault.rotatedSecret}");
StripSecretsUtils.stripClient(rep);
assertEquals("clientId", rep.getId());
assertEquals("${vault.clientSecret}", rep.getSecret());
assertEquals(2, rep.getAttributes().size());
assertEquals("clientAttr1Value", rep.getAttributes().get("clientAttr1"));
assertEquals("${vault.rotatedSecret}", rep.getAttributes().get("client.secret.rotated"));
}
@Test
public void stripBroker() {
IdentityProviderRepresentation rep = new IdentityProviderRepresentation();
rep.setInternalId("brokerId");
rep.setConfig(new HashMap<>());
rep.getConfig().put("clientSecret", "secret");
rep.getConfig().put("configParam1", "configValue1");
StripSecretsUtils.stripBroker(rep);
assertEquals("brokerId", rep.getInternalId());
assertEquals(2, rep.getConfig().size());
assertEquals("**********", rep.getConfig().get("clientSecret"));
assertEquals("configValue1", rep.getConfig().get("configParam1"));
}
@Test
public void stripComponent() {
ComponentRepresentation rep = new ComponentRepresentation();
rep.setId("componentId");
rep.setName("componentName");
rep.setProviderId("componentProviderId");
rep.setProviderType("componentProviderType");
rep.setParentId("componentParentId");
rep.setSubType("componentSubType");
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.put("secret", Arrays.asList("secretValue1", "secretValue2"));
config.put("secretWithoutValues", Arrays.asList());
config.put("nonSecret", Arrays.asList("nonSecretValue1", "nonSecretValue2"));
rep.setConfig(config);
Map<String, ProviderConfigProperty> configProperties = new HashMap<>();
configProperties.put("secret", new ProviderConfigProperty("secret", "secretLabel", "secretHelpText", "secretType", "defaultValue", true));
configProperties.put("secretWithoutValues", new ProviderConfigProperty("secretWithoutValues", "secretLabel", "secretHelpText", "secretType", "defaultValue", true));
configProperties.put("nonSecret", new ProviderConfigProperty("nonSecret", "nonSecretLabel", "nonSecretHelpText", "secretType", "defaultValue", false));
StripSecretsUtils.stripComponent(configProperties, rep);
assertEquals("componentId", rep.getId());
assertEquals("componentName", rep.getName());
assertEquals(2, rep.getConfig().get("secret").size());
assertEquals("**********", rep.getConfig().get("secret").get(0));
assertEquals("**********", rep.getConfig().get("secret").get(1));
assertEquals(1, rep.getConfig().get("secretWithoutValues").size());
assertEquals("**********", rep.getConfig().get("secretWithoutValues").get(0));
assertEquals(2, rep.getConfig().get("nonSecret").size());
assertEquals("nonSecretValue1", rep.getConfig().get("nonSecret").get(0));
assertEquals("nonSecretValue2", rep.getConfig().get("nonSecret").get(1));
}
@Test
public void stripRealm() throws IOException {
RealmRepresentation rep = new RealmRepresentation();
rep.setRealm("Master");
rep.setId("realmId");
rep.setSmtpServer(new HashMap<>());
rep.getSmtpServer().put("password", "secret");
rep.getSmtpServer().put("user", "smtpUser");
ClientRepresentation client = new ClientRepresentation();
client.setId("clientId");
client.setSecret("clientSecret");
client.setAttributes(new HashMap<>());
client.getAttributes().put("clientAttr1", "clientAttr1Value");
client.getAttributes().put("client.secret.rotated", "rotatedSecret");
rep.setClients(Arrays.asList(client));
IdentityProviderRepresentation idp = new IdentityProviderRepresentation();
idp.setProviderId("idpProviderId");
idp.setAlias("idpAlias");
idp.setConfig(new HashMap<>());
idp.getConfig().put("idpConfig1", "idpConfig1Value");
idp.getConfig().put("clientSecret", "ipdClientSecret");
rep.setIdentityProviders(Arrays.asList(idp));
UserRepresentation user = new UserRepresentation();
user.setId("userId");
user.setEnabled(true);
CredentialRepresentation userCreds = new CredentialRepresentation();
userCreds.setType(CredentialRepresentation.PASSWORD);
userCreds.setValue("userPassword");
user.setCredentials(Arrays.asList(userCreds));
rep.setUsers(Arrays.asList(user));
UserRepresentation fedUser = new UserRepresentation();
fedUser.setId("fedUserId");
fedUser.setEnabled(true);
CredentialRepresentation fedUserCreds = new CredentialRepresentation();
fedUserCreds.setType(CredentialRepresentation.PASSWORD);
fedUserCreds.setValue("fedUserPassword");
fedUser.setCredentials(Arrays.asList(fedUserCreds));
rep.setFederatedUsers(Arrays.asList(fedUser));
ComponentExportRepresentation component = new ComponentExportRepresentation();
component.setId("componentId");
component.setProviderId("componentProviderId");
component.setConfig(new MultivaluedHashMap<>());
component.getConfig().put("secret", Arrays.asList("secret1", "secret2"));
component.getConfig().put("secretWithoutValues", Collections.emptyList());
component.getConfig().put("nonSecret", Arrays.asList("nonSecret1", "nonSecret2"));
rep.setComponents(new MultivaluedHashMap<>());
rep.getComponents().put("componentExport", Arrays.asList(component));
Map<String, ProviderConfigProperty> componentConfigProperties = new HashMap<>();
componentConfigProperties.put("secret", new ProviderConfigProperty("secret", "secretLabel", "secretHelpText", "secretType", "defaultValue", true));
componentConfigProperties.put("secretWithoutValues", new ProviderConfigProperty("secretWithoutValues", "secretLabel", "secretHelpText", "secretType", "defaultValue", true));
componentConfigProperties.put("nonSecret", new ProviderConfigProperty("nonSecret", "nonSecretLabel", "nonSecretHelpText", "secretType", "defaultValue", false));
StripSecretsUtils.GetComponentPropertiesFn fnGetComponentConfigProperties = (session, providerType, providerId) -> componentConfigProperties;
StripSecretsUtils.stripRealm(null, rep, fnGetComponentConfigProperties);
assertEquals("Master", rep.getRealm());
assertEquals("realmId", rep.getId());
assertEquals(2, rep.getSmtpServer().size());
assertEquals("**********", rep.getSmtpServer().get("password"));
assertEquals("smtpUser", rep.getSmtpServer().get("user"));
assertEquals(1, rep.getClients().size());
assertEquals("clientId", rep.getClients().get(0).getId());
assertEquals("**********", rep.getClients().get(0).getSecret());
assertEquals(2, rep.getClients().get(0).getAttributes().size());
assertEquals("clientAttr1Value", rep.getClients().get(0).getAttributes().get("clientAttr1"));
assertEquals("**********", rep.getClients().get(0).getAttributes().get("client.secret.rotated"));
assertEquals(1, rep.getIdentityProviders().size());
assertEquals(2, rep.getIdentityProviders().get(0).getConfig().size());
assertEquals("idpConfig1Value", rep.getIdentityProviders().get(0).getConfig().get("idpConfig1"));
assertEquals("**********", rep.getIdentityProviders().get(0).getConfig().get("clientSecret"));
assertEquals(1, rep.getUsers().size());
assertEquals("userId", rep.getUsers().get(0).getId());
assertNull(rep.getUsers().get(0).getCredentials());
assertEquals(1, rep.getFederatedUsers().size());
assertEquals("fedUserId", rep.getFederatedUsers().get(0).getId());
assertNull(rep.getFederatedUsers().get(0).getCredentials());
assertEquals(1, rep.getComponents().size());
assertEquals(1, rep.getComponents().get("componentExport").size());
MultivaluedHashMap<String, String> componentExportConfig = rep.getComponents().get("componentExport").get(0).getConfig();
assertNotNull(componentExportConfig);
assertEquals(2, componentExportConfig.get("secret").size());
assertEquals("**********", componentExportConfig.get("secret").get(0));
assertEquals("**********", componentExportConfig.get("secret").get(1));
assertEquals(1, componentExportConfig.get("secretWithoutValues").size());
assertEquals("**********", componentExportConfig.get("secretWithoutValues").get(0));
assertEquals(2, componentExportConfig.get("nonSecret").size());
assertEquals("nonSecret1", componentExportConfig.get("nonSecret").get(0));
assertEquals("nonSecret2", componentExportConfig.get("nonSecret").get(1));
}
}

View file

@ -113,7 +113,7 @@ public class IdentityProviderResource {
throw new jakarta.ws.rs.NotFoundException(); throw new jakarta.ws.rs.NotFoundException();
} }
return ModelToRepresentation.toRepresentation(session, realm, this.identityProviderModel); return StripSecretsUtils.stripSecrets(session, ModelToRepresentation.toRepresentation(realm, this.identityProviderModel));
} }
/** /**

View file

@ -191,7 +191,7 @@ public class IdentityProvidersResource {
Function<IdentityProviderModel, IdentityProviderRepresentation> toRepresentation = briefRepresentation != null && briefRepresentation Function<IdentityProviderModel, IdentityProviderRepresentation> toRepresentation = briefRepresentation != null && briefRepresentation
? m -> ModelToRepresentation.toBriefRepresentation(realm, m) ? m -> ModelToRepresentation.toBriefRepresentation(realm, m)
: m -> ModelToRepresentation.toRepresentation(session, realm, m); : m -> StripSecretsUtils.stripSecrets(session, ModelToRepresentation.toRepresentation(realm, m));
Stream<IdentityProviderModel> stream = realm.getIdentityProvidersStream().sorted(new IdPComparator()); Stream<IdentityProviderModel> stream = realm.getIdentityProvidersStream().sorted(new IdPComparator());
if (!StringUtil.isBlank(search)) { if (!StringUtil.isBlank(search)) {
@ -240,7 +240,7 @@ public class IdentityProvidersResource {
representation.setInternalId(identityProvider.getInternalId()); representation.setInternalId(identityProvider.getInternalId());
adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), identityProvider.getAlias()) adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), identityProvider.getAlias())
.representation(representation).success(); .representation(StripSecretsUtils.stripSecrets(session, representation)).success();
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(representation.getAlias()).build()).build(); return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(representation.getAlias()).build()).build();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {

View file

@ -482,6 +482,8 @@ public class ClientTest extends AbstractAdminTest {
realm.clients().get(client.getId()).update(newClient); realm.clients().get(client.getId()).update(newClient);
newClient.setSecret("**********"); // secrets are masked in events
assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.clientResourcePath(client.getId()), newClient, ResourceType.CLIENT); assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.clientResourcePath(client.getId()), newClient, ResourceType.CLIENT);
storedClient = realm.clients().get(client.getId()).toRepresentation(); storedClient = realm.clients().get(client.getId()).toRepresentation();
@ -540,6 +542,7 @@ public class ClientTest extends AbstractAdminTest {
getCleanup().addClientUuid(id); getCleanup().addClientUuid(id);
response.close(); response.close();
client.setSecret("**********"); // secrets are masked in events
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(id), client, ResourceType.CLIENT); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(id), client, ResourceType.CLIENT);
client.setId(id); client.setId(id);

View file

@ -258,7 +258,7 @@ public class UserTest extends AbstractAdminTest {
private void updateUser(UserResource user, UserRepresentation userRep) { private void updateUser(UserResource user, UserRepresentation userRep) {
user.update(userRep); user.update(userRep);
List<CredentialRepresentation> credentials = userRep.getCredentials(); List<CredentialRepresentation> credentials = userRep.getCredentials();
assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.userResourcePath(userRep.getId()), StripSecretsUtils.strip(userRep), ResourceType.USER); assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, AdminEventPaths.userResourcePath(userRep.getId()), StripSecretsUtils.stripSecrets(null, userRep), ResourceType.USER);
userRep.setCredentials(credentials); userRep.setCredentials(credentials);
} }

View file

@ -1055,6 +1055,8 @@ public class RealmTest extends AbstractAdminTest {
String clientDbId = ApiUtil.getCreatedId(resp); String clientDbId = ApiUtil.getCreatedId(resp);
getCleanup().addClientUuid(clientDbId); getCleanup().addClientUuid(clientDbId);
resp.close(); resp.close();
client.setSecret("**********"); // secrets are masked in events
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(clientDbId), client, ResourceType.CLIENT); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(clientDbId), client, ResourceType.CLIENT);
oauth.realm(REALM_NAME); oauth.realm(REALM_NAME);
@ -1086,6 +1088,8 @@ public class RealmTest extends AbstractAdminTest {
String clientDbId = ApiUtil.getCreatedId(resp); String clientDbId = ApiUtil.getCreatedId(resp);
getCleanup().addClientUuid(clientDbId); getCleanup().addClientUuid(clientDbId);
resp.close(); resp.close();
client.setSecret("**********"); // secrets are masked in events
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(clientDbId), client, ResourceType.CLIENT); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientResourcePath(clientDbId), client, ResourceType.CLIENT);
} }

View file

@ -30,9 +30,11 @@ import org.keycloak.exportimport.Strategy;
import org.keycloak.exportimport.dir.DirExportProvider; import org.keycloak.exportimport.dir.DirExportProvider;
import org.keycloak.exportimport.dir.DirExportProviderFactory; import org.keycloak.exportimport.dir.DirExportProviderFactory;
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory; import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation; import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation; import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
@ -50,6 +52,7 @@ import org.keycloak.userprofile.DeclarativeUserProfileProvider;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
@ -66,6 +69,7 @@ import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
@ -208,6 +212,17 @@ public class ExportImportTest extends AbstractKeycloakTest {
testingClient.testing().exportImport().setFile(targetFilePath); testingClient.testing().exportImport().setFile(targetFilePath);
testFullExportImport(); testFullExportImport();
assertExportContainsGoogleClientSecret(targetFilePath);
}
private static void assertExportContainsGoogleClientSecret(String targetFilePath) throws IOException {
assertTrue("Expected an export file to exist", new File(targetFilePath).exists());
Map<String, RealmRepresentation> realms = ImportUtils.getRealmsFromStream(JsonSerialization.mapper, new FileInputStream(new File(targetFilePath)));
List<IdentityProviderRepresentation> idps = realms.get("test-realm").getIdentityProviders();
IdentityProviderRepresentation googleIdp = idps.stream().filter(idp -> idp.getAlias().equals("google1")).findFirst().get();
assertNotNull(googleIdp);
assertEquals("googleSecret", googleIdp.getConfig().get("clientSecret"));
} }
@Test @Test

View file

@ -293,7 +293,7 @@ public class ExportImportUtil {
Assert.assertEquals("google", google.getProviderId()); Assert.assertEquals("google", google.getProviderId());
Assert.assertTrue(google.isEnabled()); Assert.assertTrue(google.isEnabled());
Assert.assertEquals("googleId", google.getConfig().get("clientId")); Assert.assertEquals("googleId", google.getConfig().get("clientId"));
Assert.assertEquals("googleSecret", google.getConfig().get("clientSecret")); Assert.assertEquals("**********", google.getConfig().get("clientSecret")); // secret is masked in GET call
////////////////// //////////////////
// Test federation providers // Test federation providers