-
Notifications
You must be signed in to change notification settings - Fork 126
/
ClientNormalizationService.java
140 lines (126 loc) · 6.76 KB
/
ClientNormalizationService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/*-
* ---license-start
* keycloak-config-cli
* ---
* Copyright (C) 2017 - 2022 adorsys GmbH & Co. KG @ https://adorsys.com
* ---
* 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.
* ---license-end
*/
package de.adorsys.keycloak.config.service.normalize;
import de.adorsys.keycloak.config.provider.BaselineProvider;
import de.adorsys.keycloak.config.util.JaversUtil;
import org.javers.core.Javers;
import org.javers.core.diff.changetype.PropertyChange;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@ConditionalOnProperty(prefix = "run", name = "operation", havingValue = "NORMALIZE")
public class ClientNormalizationService {
private static final Logger logger = LoggerFactory.getLogger(ClientNormalizationService.class);
private final Javers javers;
private final BaselineProvider baselineProvider;
private final JaversUtil javersUtil;
private final ProtocolMapperNormalizationService protocolMapperNormalizationService;
public ClientNormalizationService(Javers javers,
BaselineProvider baselineProvider,
JaversUtil javersUtil,
ProtocolMapperNormalizationService protocolMapperNormalizationService) {
this.javers = javers;
this.baselineProvider = baselineProvider;
this.javersUtil = javersUtil;
this.protocolMapperNormalizationService = protocolMapperNormalizationService;
}
public List<ClientRepresentation> normalizeClients(RealmRepresentation exportedRealm, RealmRepresentation baselineRealm) {
List<ClientRepresentation> exportedOrEmpty = exportedRealm.getClients() == null ? List.of() : exportedRealm.getClients();
List<ClientRepresentation> baselineOrEmpty = baselineRealm.getClients() == null ? List.of() : baselineRealm.getClients();
var exportedClientMap = new HashMap<String, ClientRepresentation>();
for (var exportedClient : exportedOrEmpty) {
exportedClientMap.put(exportedClient.getClientId(), exportedClient);
}
var baselineClientMap = new HashMap<String, ClientRepresentation>();
var clients = new ArrayList<ClientRepresentation>();
for (var baselineRealmClient : baselineOrEmpty) {
var clientId = baselineRealmClient.getClientId();
baselineClientMap.put(clientId, baselineRealmClient);
var exportedClient = exportedClientMap.get(clientId);
if (exportedClient == null) {
logger.warn("Default realm client '{}' was deleted in exported realm. It may be reintroduced during import!", clientId);
/*
* Here we need to define a configuration parameter: If we want the import *not* to reintroduce default clients that were
* deleted, we need to add *all* clients, not just default clients to the dump. Then during import, set the mode that
* makes clients fully managed, so that *only* clients that are in the dump end up in the realm
*/
continue;
}
if (clientChanged(exportedClient, baselineRealmClient)) {
// We know the client has changed in some way. Now, compare it to a default client to minimize it
clients.add(normalizeClient(exportedClient, exportedRealm.getKeycloakVersion()));
}
}
// Now iterate over all the clients that are *not* default clients
for (Map.Entry<String, ClientRepresentation> e : exportedClientMap.entrySet()) {
if (!baselineClientMap.containsKey(e.getKey())) {
clients.add(normalizeClient(e.getValue(), exportedRealm.getKeycloakVersion()));
}
}
return clients;
}
public ClientRepresentation normalizeClient(ClientRepresentation client, String keycloakVersion) {
var clientId = client.getClientId();
var baselineClient = baselineProvider.getClient(keycloakVersion, clientId);
var diff = javers.compare(baselineClient, client);
var normalizedClient = new ClientRepresentation();
for (var change : diff.getChangesByType(PropertyChange.class)) {
javersUtil.applyChange(normalizedClient, change);
}
var mappers = client.getProtocolMappers();
normalizedClient.setProtocolMappers(mappers);
if (mappers != null) {
for (var mapper : mappers) {
mapper.setId(null);
}
}
normalizedClient.setAuthorizationSettings(client.getAuthorizationSettings());
normalizedClient.setClientId(clientId);
return normalizedClient;
}
public boolean clientChanged(ClientRepresentation exportedClient, ClientRepresentation baselineClient) {
var diff = javers.compare(baselineClient, exportedClient);
if (diff.hasChanges()) {
return true;
}
if (protocolMappersChanged(exportedClient.getProtocolMappers(), baselineClient.getProtocolMappers())) {
return true;
}
return authorizationSettingsChanged(exportedClient.getAuthorizationSettings(), baselineClient.getAuthorizationSettings());
}
public boolean protocolMappersChanged(List<ProtocolMapperRepresentation> exportedMappers, List<ProtocolMapperRepresentation> baselineMappers) {
// CompareCollections doesn't handle nulls gracefully
return javers.compareCollections(baselineMappers == null ? List.of() : baselineMappers,
exportedMappers == null ? List.of() : exportedMappers, ProtocolMapperRepresentation.class).hasChanges();
}
public boolean authorizationSettingsChanged(ResourceServerRepresentation exportedSettings, ResourceServerRepresentation baselineSettings) {
return javers.compare(baselineSettings, exportedSettings).hasChanges();
}
}