forked from keycloak/keycloak
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ClientStorageManager.java
296 lines (248 loc) · 13.2 KB
/
ClientStorageManager.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/*
* 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.storage;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.common.util.reflections.Types;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LegacyRealmModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.storage.client.ClientLookupProvider;
import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.client.ClientStorageProviderFactory;
import org.keycloak.storage.client.ClientStorageProviderModel;
import org.keycloak.utils.ServicesUtils;
import java.util.Objects;
import java.util.function.Function;
import java.util.Set;
import java.util.stream.Stream;
import org.keycloak.models.ClientScopeModel;
import static org.keycloak.utils.StreamsUtil.paginatedStream;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ClientStorageManager implements ClientProvider {
private static final Logger logger = Logger.getLogger(ClientStorageManager.class);
protected KeycloakSession session;
private long clientStorageProviderTimeout;
private ClientProvider localStorage() {
return session.getProvider(ClientProvider.class);
}
public static boolean isStorageProviderEnabled(RealmModel realm, String providerId) {
ClientStorageProviderModel model = getStorageProviderModel(realm, providerId);
return model.isEnabled();
}
public static ClientStorageProviderModel getStorageProviderModel(RealmModel realm, String componentId) {
ComponentModel model = realm.getComponent(componentId);
if (model == null) return null;
return new ClientStorageProviderModel(model);
}
public static ClientStorageProvider getStorageProvider(KeycloakSession session, RealmModel realm, String componentId) {
ComponentModel model = realm.getComponent(componentId);
if (model == null) return null;
ClientStorageProviderModel storageModel = new ClientStorageProviderModel(model);
ClientStorageProviderFactory factory = (ClientStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(ClientStorageProvider.class, model.getProviderId());
if (factory == null) {
throw new ModelException("Could not find ClientStorageProviderFactory for: " + model.getProviderId());
}
return getStorageProviderInstance(session, storageModel, factory);
}
private static <T> Stream<ClientStorageProviderModel> getStorageProviders(RealmModel realm, KeycloakSession session, Class<T> type) {
return ((LegacyRealmModel) realm).getClientStorageProvidersStream()
.filter(model -> {
ClientStorageProviderFactory factory = getClientStorageProviderFactory(model, session);
if (factory == null) {
logger.warnv("Configured ClientStorageProvider {0} of provider id {1} does not exist in realm {2}",
model.getName(), model.getProviderId(), realm.getName());
return false;
} else {
return Types.supports(type, factory, ClientStorageProviderFactory.class);
}
});
}
public static ClientStorageProvider getStorageProviderInstance(KeycloakSession session, ClientStorageProviderModel model, ClientStorageProviderFactory factory) {
ClientStorageProvider instance = (ClientStorageProvider)session.getAttribute(model.getId());
if (instance != null) return instance;
instance = factory.create(session, model);
if (instance == null) {
throw new IllegalStateException("ClientStorageProvideFactory (of type " + factory.getClass().getName() + ") produced a null instance");
}
session.enlistForClose(instance);
session.setAttribute(model.getId(), instance);
return instance;
}
public static <T> Stream<T> getStorageProviders(KeycloakSession session, RealmModel realm, Class<T> type) {
return getStorageProviders(realm, session, type)
.map(model -> type.cast(getStorageProviderInstance(session, model, getClientStorageProviderFactory(model, session))));
}
private static ClientStorageProviderFactory getClientStorageProviderFactory(ClientStorageProviderModel model, KeycloakSession session) {
return (ClientStorageProviderFactory) session.getKeycloakSessionFactory()
.getProviderFactory(ClientStorageProvider.class, model.getProviderId());
}
public static <T> Stream<T> getEnabledStorageProviders(KeycloakSession session, RealmModel realm, Class<T> type) {
return getStorageProviders(realm, session, type)
.filter(ClientStorageProviderModel::isEnabled)
.map(model -> type.cast(getStorageProviderInstance(session, model, getClientStorageProviderFactory(model, session))));
}
public static boolean hasEnabledStorageProviders(KeycloakSession session, RealmModel realm, Class<?> type) {
return getStorageProviders(realm, session, type).anyMatch(ClientStorageProviderModel::isEnabled);
}
public ClientStorageManager(KeycloakSession session, long clientStorageProviderTimeout) {
this.session = session;
this.clientStorageProviderTimeout = clientStorageProviderTimeout;
}
@Override
public ClientModel getClientById(RealmModel realm, String id) {
StorageId storageId = new StorageId(id);
if (storageId.getProviderId() == null) {
return localStorage().getClientById(realm, id);
}
ClientLookupProvider provider = (ClientLookupProvider)getStorageProvider(session, realm, storageId.getProviderId());
if (provider == null) return null;
if (!isStorageProviderEnabled(realm, storageId.getProviderId())) return null;
return provider.getClientById(realm, id);
}
@Override
public ClientModel getClientByClientId(RealmModel realm, String clientId) {
ClientModel client = localStorage().getClientByClientId(realm, clientId);
if (client != null) {
return client;
}
return getEnabledStorageProviders(session, realm, ClientLookupProvider.class)
.map(provider -> provider.getClientByClientId(realm, clientId))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
@Override
public Stream<ClientModel> searchClientsByClientIdStream(RealmModel realm, String clientId, Integer firstResult, Integer maxResults) {
return query((p, f, m) -> p.searchClientsByClientIdStream(realm, clientId, f, m), realm, firstResult, maxResults);
}
@Override
public Stream<ClientModel> searchClientsByAttributes(RealmModel realm, Map<String, String> attributes, Integer firstResult, Integer maxResults) {
return query((p, f, m) -> p.searchClientsByAttributes(realm, attributes, f, m), realm, firstResult, maxResults);
}
@Override
public Stream<ClientModel> searchClientsByAuthenticationFlowBindingOverrides(RealmModel realm, Map<String, String> overrides, Integer firstResult, Integer maxResults) {
return query((p, f, m) -> p.searchClientsByAuthenticationFlowBindingOverrides(realm, overrides, f, m), realm, firstResult, maxResults);
}
@FunctionalInterface
interface PaginatedQuery {
Stream<ClientModel> query(ClientLookupProvider provider, Integer firstResult, Integer maxResults);
}
protected Stream<ClientModel> query(PaginatedQuery paginatedQuery, RealmModel realm, Integer firstResult, Integer maxResults) {
if (maxResults != null && maxResults == 0) return Stream.empty();
// when there are external providers involved, we can't do pagination at the lower data layer as we don't know
// how many results there will be; i.e. we need to query the clients without paginating them and perform pagination
// later at this level
if (hasEnabledStorageProviders(session, realm, ClientLookupProvider.class)) {
Stream<ClientLookupProvider> providersStream = Stream.concat(Stream.of(localStorage()), getEnabledStorageProviders(session, realm, ClientLookupProvider.class));
/*
Obtaining clients from an external client storage is time-bounded. In case the external client storage
isn't available at least clients from a local storage are returned, otherwise both storages are used. For this purpose
the {@link org.keycloak.services.DefaultKeycloakSessionFactory#getClientStorageProviderTimeout()} property is used.
Default value is 3000 milliseconds and it's configurable.
See {@link org.keycloak.services.DefaultKeycloakSessionFactory} for details.
*/
Function<ClientLookupProvider, Stream<? extends ClientModel>> performQueryWithTimeBound = (p) -> {
if (p instanceof ClientStorageProvider) {
return ServicesUtils.timeBound(session, clientStorageProviderTimeout, p2 -> paginatedQuery.query((ClientLookupProvider) p2, null, null)).apply(p);
}
else {
return paginatedQuery.query(p, null, null);
}
};
Stream<ClientModel> res = providersStream.flatMap(performQueryWithTimeBound);
return paginatedStream(res, firstResult, maxResults);
}
else {
return paginatedQuery.query(localStorage(), firstResult, maxResults);
}
}
@Override
public Map<String, ClientScopeModel> getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) {
StorageId storageId = new StorageId(client.getId());
if (storageId.getProviderId() == null) {
return localStorage().getClientScopes(realm, client, defaultScopes);
}
ClientLookupProvider provider = (ClientLookupProvider)getStorageProvider(session, client.getRealm(), storageId.getProviderId());
if (provider == null) return null;
if (!isStorageProviderEnabled(client.getRealm(), storageId.getProviderId())) return null;
return provider.getClientScopes(realm, client, defaultScopes);
}
@Override
public ClientModel addClient(RealmModel realm, String clientId) {
return localStorage().addClient(realm, clientId);
}
@Override
public ClientModel addClient(RealmModel realm, String id, String clientId) {
return localStorage().addClient(realm, id, clientId);
}
@Override
public Stream<ClientModel> getClientsStream(RealmModel realm, Integer firstResult, Integer maxResults) {
return localStorage().getClientsStream(realm, firstResult, maxResults);
}
@Override
public Stream<ClientModel> getClientsStream(RealmModel realm) {
return localStorage().getClientsStream(realm);
}
@Override
public long getClientsCount(RealmModel realm) {
return localStorage().getClientsCount(realm);
}
@Override
public Stream<ClientModel> getAlwaysDisplayInConsoleClientsStream(RealmModel realm) {
return localStorage().getAlwaysDisplayInConsoleClientsStream(realm);
}
@Override
public void removeClients(RealmModel realm) {
localStorage().removeClients(realm);
}
@Override
public void addClientScopes(RealmModel realm, ClientModel client, Set<ClientScopeModel> clientScopes, boolean defaultScope) {
if (!StorageId.isLocalStorage(client.getId())) {
throw new RuntimeException("Federated clients do not support this operation");
}
localStorage().addClientScopes(realm, client, clientScopes, defaultScope);
}
@Override
public void removeClientScope(RealmModel realm, ClientModel client, ClientScopeModel clientScope) {
if (!StorageId.isLocalStorage(client.getId())) {
throw new RuntimeException("Federated clients do not support this operation");
}
localStorage().removeClientScope(realm, client, clientScope);
}
@Override
public Map<ClientModel, Set<String>> getAllRedirectUrisOfEnabledClients(RealmModel realm) {
return localStorage().getAllRedirectUrisOfEnabledClients(realm);
}
@Override
public void close() {
}
@Override
public boolean removeClient(RealmModel realm, String id) {
if (!StorageId.isLocalStorage(id)) {
throw new RuntimeException("Federated clients do not support this operation");
}
return localStorage().removeClient(realm, id);
}
}