KEYCLOAK-17021 Client Scope map store
This commit is contained in:
parent
2c64a56072
commit
6e501946b1
55 changed files with 2271 additions and 150 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -160,7 +160,7 @@ jobs:
|
|||
run: |
|
||||
declare -A PARAMS TESTGROUP
|
||||
PARAMS["quarkus"]="-Pauth-server-quarkus"
|
||||
PARAMS["undertow-map"]="-Pauth-server-undertow -Dkeycloak.client.provider=map -Dkeycloak.group.provider=map -Dkeycloak.role.provider=map -Dkeycloak.authSession.provider=map -Dkeycloak.user.provider=map"
|
||||
PARAMS["undertow-map"]="-Pauth-server-undertow -Dkeycloak.client.provider=map -Dkeycloak.group.provider=map -Dkeycloak.role.provider=map -Dkeycloak.authSession.provider=map -Dkeycloak.user.provider=map -Dkeycloak.clientScope.provider=map"
|
||||
PARAMS["wildfly"]="-Pauth-server-wildfly"
|
||||
TESTGROUP["group1"]="-Dtest=!**.crossdc.**,!**.cluster.**,%regex[org.keycloak.testsuite.(a[abc]|ad[a-l]|[^a-q]).*]" # Tests alphabetically before admin tests and those after "r"
|
||||
TESTGROUP["group2"]="-Dtest=!**.crossdc.**,!**.cluster.**,%regex[org.keycloak.testsuite.(ad[^a-l]|a[^a-d]|b).*]" # Admin tests and those starting with "b"
|
||||
|
|
|
@ -126,7 +126,7 @@ public class ClientAdapter implements ClientModel, CachedObject {
|
|||
|
||||
Map<String, ClientScopeModel> clientScopes = new HashMap<>();
|
||||
for (String scopeId : clientScopeIds) {
|
||||
ClientScopeModel clientScope = cacheSession.getClientScopeById(scopeId, cachedRealm);
|
||||
ClientScopeModel clientScope = cacheSession.getClientScopeById(cachedRealm, scopeId);
|
||||
if (clientScope != null) {
|
||||
if (!filterByProtocol || clientScope.getProtocol().equals(clientProtocol)) {
|
||||
clientScopes.put(clientScope.getName(), clientScope);
|
||||
|
|
|
@ -47,8 +47,8 @@ public class ClientScopeAdapter implements ClientScopeModel {
|
|||
|
||||
private void getDelegateForUpdate() {
|
||||
if (updated == null) {
|
||||
cacheSession.registerClientScopeInvalidation(cached.getId());
|
||||
updated = cacheSession.getRealmDelegate().getClientScopeById(cached.getId(), cachedRealm);
|
||||
cacheSession.registerClientScopeInvalidation(cached.getId(), cachedRealm.getId());
|
||||
updated = cacheSession.getClientScopeDelegate().getClientScopeById(cachedRealm, cached.getId());
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public class ClientScopeAdapter implements ClientScopeModel {
|
|||
protected boolean isUpdated() {
|
||||
if (updated != null) return true;
|
||||
if (!invalidated) return false;
|
||||
updated = cacheSession.getRealmDelegate().getClientScopeById(cached.getId(), cachedRealm);
|
||||
updated = cacheSession.getClientScopeDelegate().getClientScopeById(cachedRealm, cached.getId());
|
||||
if (updated == null) throw new IllegalStateException("Not found in database");
|
||||
return true;
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ public class ClientScopeAdapter implements ClientScopeModel {
|
|||
return cached.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return cachedRealm;
|
||||
}
|
||||
|
@ -155,22 +156,26 @@ public class ClientScopeAdapter implements ClientScopeModel {
|
|||
updated.setProtocol(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getScopeMappingsStream() {
|
||||
if (isUpdated()) return updated.getScopeMappingsStream();
|
||||
return cached.getScope().stream()
|
||||
.map(id -> cacheSession.getRoleById(cachedRealm, id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(RoleModel role) {
|
||||
getDelegateForUpdate();
|
||||
updated.addScopeMapping(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteScopeMapping(RoleModel role) {
|
||||
getDelegateForUpdate();
|
||||
updated.deleteScopeMapping(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRealmScopeMappingsStream() {
|
||||
return getScopeMappingsStream().filter(r -> RoleUtils.isRealmRole(r, cachedRealm));
|
||||
}
|
||||
|
|
|
@ -1457,7 +1457,7 @@ public class RealmAdapter implements CachedRealmModel {
|
|||
public Stream<ClientScopeModel> getClientScopesStream() {
|
||||
if (isUpdated()) return updated.getClientScopesStream();
|
||||
return cached.getClientScopes().stream().map(scope -> {
|
||||
ClientScopeModel model = cacheSession.getClientScopeById(scope, this);
|
||||
ClientScopeModel model = cacheSession.getClientScopeById(this, scope);
|
||||
if (model == null) {
|
||||
throw new IllegalStateException("Cached clientScope not found: " + scope);
|
||||
}
|
||||
|
@ -1467,31 +1467,31 @@ public class RealmAdapter implements CachedRealmModel {
|
|||
|
||||
@Override
|
||||
public ClientScopeModel addClientScope(String name) {
|
||||
getDelegateForUpdate();
|
||||
ClientScopeModel app = updated.addClientScope(name);
|
||||
cacheSession.registerClientScopeInvalidation(app.getId());
|
||||
return app;
|
||||
RealmModel realm = getDelegateForUpdate();
|
||||
ClientScopeModel clientScope = updated.addClientScope(name);
|
||||
cacheSession.registerClientScopeInvalidation(clientScope.getId(), realm.getId());
|
||||
return clientScope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addClientScope(String id, String name) {
|
||||
getDelegateForUpdate();
|
||||
ClientScopeModel app = updated.addClientScope(id, name);
|
||||
cacheSession.registerClientScopeInvalidation(app.getId());
|
||||
return app;
|
||||
RealmModel realm = getDelegateForUpdate();
|
||||
ClientScopeModel clientScope = updated.addClientScope(id, name);
|
||||
cacheSession.registerClientScopeInvalidation(clientScope.getId(), realm.getId());
|
||||
return clientScope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeClientScope(String id) {
|
||||
cacheSession.registerClientScopeInvalidation(id);
|
||||
getDelegateForUpdate();
|
||||
RealmModel realm = getDelegateForUpdate();
|
||||
cacheSession.registerClientScopeInvalidation(id, realm.getId());
|
||||
return updated.removeClientScope(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel getClientScopeById(String id) {
|
||||
if (isUpdated()) return updated.getClientScopeById(id);
|
||||
return cacheSession.getClientScopeById(id, this);
|
||||
return cacheSession.getClientScopeById(this, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1511,7 +1511,7 @@ public class RealmAdapter implements CachedRealmModel {
|
|||
if (isUpdated()) return updated.getDefaultClientScopesStream(defaultScope);
|
||||
List<String> clientScopeIds = defaultScope ? cached.getDefaultDefaultClientScopes() : cached.getOptionalDefaultClientScopes();
|
||||
return clientScopeIds.stream()
|
||||
.map(scope -> cacheSession.getClientScopeById(scope, this))
|
||||
.map(scope -> cacheSession.getClientScopeById(this, scope))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,19 @@ public class RealmCacheManager extends CacheManager {
|
|||
addInvalidations(HasRolePredicate.create().role(id), invalidations);
|
||||
}
|
||||
|
||||
public void clientScopeAdded(String realmId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getClientScopesCacheKey(realmId));
|
||||
}
|
||||
|
||||
public void clientScopeUpdated(String realmId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getClientScopesCacheKey(realmId));
|
||||
}
|
||||
|
||||
public void clientScopeRemoval(String realmId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getClientScopesCacheKey(realmId));
|
||||
addInvalidations(InRealmPredicate.create().realm(realmId), invalidations);
|
||||
}
|
||||
|
||||
public void groupQueriesInvalidations(String realmId, Set<String> invalidations) {
|
||||
invalidations.add(RealmCacheSession.getGroupsQueryCacheKey(realmId));
|
||||
invalidations.add(RealmCacheSession.getTopGroupsQueryCacheKey(realmId));
|
||||
|
|
|
@ -101,6 +101,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
protected KeycloakSession session;
|
||||
protected RealmProvider realmDelegate;
|
||||
protected ClientProvider clientDelegate;
|
||||
protected ClientScopeProvider clientScopeDelegate;
|
||||
protected GroupProvider groupDelegate;
|
||||
protected RoleProvider roleDelegate;
|
||||
protected boolean transactionActive;
|
||||
|
@ -158,6 +159,12 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
clientDelegate = session.clientStorageManager();
|
||||
return clientDelegate;
|
||||
}
|
||||
public ClientScopeProvider getClientScopeDelegate() {
|
||||
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
|
||||
if (clientScopeDelegate != null) return clientScopeDelegate;
|
||||
clientScopeDelegate = session.clientScopeStorageManager();
|
||||
return clientScopeDelegate;
|
||||
}
|
||||
public RoleProvider getRoleDelegate() {
|
||||
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
|
||||
if (roleDelegate != null) return roleDelegate;
|
||||
|
@ -194,10 +201,9 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void registerClientScopeInvalidation(String id) {
|
||||
public void registerClientScopeInvalidation(String id, String realmId) {
|
||||
invalidateClientScope(id);
|
||||
// Note: Adding/Removing client template is supposed to invalidate CachedRealm as well, so the list of clientScopes is invalidated.
|
||||
// But separate RealmUpdatedEvent will be sent for it. So ClientTemplateEvent don't need to take care of it.
|
||||
cache.clientScopeUpdated(realmId, invalidations);
|
||||
invalidationEvents.add(ClientTemplateEvent.create(id));
|
||||
}
|
||||
|
||||
|
@ -532,6 +538,10 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
return realm + ".groups";
|
||||
}
|
||||
|
||||
static String getClientScopesCacheKey(String realm) {
|
||||
return realm + ".clientscopes";
|
||||
}
|
||||
|
||||
static String getTopGroupsQueryCacheKey(String realm) {
|
||||
return realm + ".top.groups";
|
||||
}
|
||||
|
@ -595,6 +605,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
public void close() {
|
||||
if (realmDelegate != null) realmDelegate.close();
|
||||
if (clientDelegate != null) clientDelegate.close();
|
||||
if (clientScopeDelegate != null) clientScopeDelegate.close();
|
||||
if (roleDelegate != null) roleDelegate.close();
|
||||
}
|
||||
|
||||
|
@ -1192,7 +1203,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel getClientScopeById(String id, RealmModel realm) {
|
||||
public ClientScopeModel getClientScopeById(RealmModel realm, String id) {
|
||||
CachedClientScope cached = cache.get(id, CachedClientScope.class);
|
||||
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||
cached = null;
|
||||
|
@ -1200,13 +1211,13 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
if (cached == null) {
|
||||
Long loaded = cache.getCurrentRevision(id);
|
||||
ClientScopeModel model = getRealmDelegate().getClientScopeById(id, realm);
|
||||
ClientScopeModel model = getClientScopeDelegate().getClientScopeById(realm, id);
|
||||
if (model == null) return null;
|
||||
if (invalidations.contains(id)) return model;
|
||||
cached = new CachedClientScope(loaded, realm, model);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
} else if (invalidations.contains(id)) {
|
||||
return getRealmDelegate().getClientScopeById(id, realm);
|
||||
return getClientScopeDelegate().getClientScopeById(realm, id);
|
||||
} else if (managedClientScopes.containsKey(id)) {
|
||||
return managedClientScopes.get(id);
|
||||
}
|
||||
|
@ -1215,6 +1226,85 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ClientScopeModel> getClientScopesStream(RealmModel realm) {
|
||||
String cacheKey = getClientScopesCacheKey(realm.getId());
|
||||
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId());
|
||||
if (queryDB) {
|
||||
return getClientScopeDelegate().getClientScopesStream(realm);
|
||||
}
|
||||
|
||||
ClientScopeListQuery query = cache.get(cacheKey, ClientScopeListQuery.class);
|
||||
if (query != null) {
|
||||
logger.tracev("getClientScopesStream cache hit: {0}", realm.getName());
|
||||
}
|
||||
|
||||
if (query == null) {
|
||||
Long loaded = cache.getCurrentRevision(cacheKey);
|
||||
Set<ClientScopeModel> model = getClientScopeDelegate().getClientScopesStream(realm).collect(Collectors.toSet());
|
||||
if (model == null) return null;
|
||||
Set<String> ids = model.stream().map(ClientScopeModel::getId).collect(Collectors.toSet());
|
||||
query = new ClientScopeListQuery(loaded, cacheKey, realm, ids);
|
||||
logger.tracev("adding client scopes cache miss: realm {0} key {1}", realm.getName(), cacheKey);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
return model.stream();
|
||||
}
|
||||
Set<ClientScopeModel> list = new HashSet<>();
|
||||
for (String id : query.getClientScopes()) {
|
||||
ClientScopeModel clientScope = session.clientScopes().getClientScopeById(realm, id);
|
||||
if (clientScope == null) {
|
||||
invalidations.add(cacheKey);
|
||||
return getClientScopeDelegate().getClientScopesStream(realm);
|
||||
}
|
||||
list.add(clientScope);
|
||||
}
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addClientScope(RealmModel realm, String name) {
|
||||
ClientScopeModel clientScope = getClientScopeDelegate().addClientScope(realm, name);
|
||||
return addedClientScope(realm, clientScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addClientScope(RealmModel realm, String id, String name) {
|
||||
ClientScopeModel clientScope = getClientScopeDelegate().addClientScope(realm, id, name);
|
||||
return addedClientScope(realm, clientScope);
|
||||
}
|
||||
|
||||
private ClientScopeModel addedClientScope(RealmModel realm, ClientScopeModel clientScope) {
|
||||
logger.tracef("Added client scope %s", clientScope.getId());
|
||||
|
||||
invalidateClientScope(clientScope.getId());
|
||||
// this is needed so that a client scope that hasn't been committed isn't cached in a query
|
||||
listInvalidations.add(realm.getId());
|
||||
|
||||
invalidationEvents.add(ClientScopeAddedEvent.create(clientScope.getId(), realm.getId()));
|
||||
cache.clientScopeAdded(realm.getId(), invalidations);
|
||||
return clientScope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeClientScope(RealmModel realm, String id) {
|
||||
//removeClientScope can throw ModelException in case the client scope us used so invalidate only if the removal is succesful
|
||||
if (getClientScopeDelegate().removeClientScope(realm, id)) {
|
||||
listInvalidations.add(realm.getId());
|
||||
|
||||
invalidateClientScope(id);
|
||||
invalidationEvents.add(ClientScopeRemovedEvent.create(id, realm.getId()));
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScopes(RealmModel realm) {
|
||||
realm.getClientScopesStream().map(ClientScopeModel::getId).forEach(id -> removeClientScope(realm, id));
|
||||
}
|
||||
|
||||
// Don't cache ClientInitialAccessModel for now
|
||||
@Override
|
||||
public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2021 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.cache.infinispan.entities;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class ClientScopeListQuery extends AbstractRevisioned implements ClientScopeQuery {
|
||||
private final Set<String> clientScopes;
|
||||
private final String realm;
|
||||
private final String realmName;
|
||||
|
||||
public ClientScopeListQuery(Long revisioned, String id, RealmModel realm, Set<String> clientScopes) {
|
||||
super(revisioned, id);
|
||||
this.realm = realm.getId();
|
||||
this.realmName = realm.getName();
|
||||
this.clientScopes = clientScopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getClientScopes() {
|
||||
return clientScopes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientScopeListQuery{" +
|
||||
"id='" + getId() + "'" +
|
||||
", realmName='" + realmName + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2021 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.cache.infinispan.entities;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface ClientScopeQuery extends InRealm {
|
||||
Set<String> getClientScopes();
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2021 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.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import org.infinispan.commons.marshall.Externalizer;
|
||||
import org.infinispan.commons.marshall.MarshallUtil;
|
||||
import org.infinispan.commons.marshall.SerializeWith;
|
||||
|
||||
@SerializeWith(ClientScopeAddedEvent.ExternalizerImpl.class)
|
||||
public class ClientScopeAddedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String clientScopeId;
|
||||
private String realmId;
|
||||
|
||||
public static ClientScopeAddedEvent create(String clientScopeId, String realmId) {
|
||||
ClientScopeAddedEvent event = new ClientScopeAddedEvent();
|
||||
event.clientScopeId = clientScopeId;
|
||||
event.realmId = realmId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return clientScopeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ClientScopeAddedEvent [ clientScopeId=%s, realmId=%s ]", clientScopeId, realmId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.clientScopeAdded(realmId, invalidations);
|
||||
}
|
||||
|
||||
public static class ExternalizerImpl implements Externalizer<ClientScopeAddedEvent> {
|
||||
|
||||
private static final int VERSION_1 = 1;
|
||||
|
||||
@Override
|
||||
public void writeObject(ObjectOutput output, ClientScopeAddedEvent obj) throws IOException {
|
||||
output.writeByte(VERSION_1);
|
||||
|
||||
MarshallUtil.marshallString(obj.clientScopeId, output);
|
||||
MarshallUtil.marshallString(obj.realmId, output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeAddedEvent readObject(ObjectInput input) throws IOException, ClassNotFoundException {
|
||||
switch (input.readByte()) {
|
||||
case VERSION_1:
|
||||
return readObjectVersion1(input);
|
||||
default:
|
||||
throw new IOException("Unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
public ClientScopeAddedEvent readObjectVersion1(ObjectInput input) throws IOException, ClassNotFoundException {
|
||||
ClientScopeAddedEvent res = new ClientScopeAddedEvent();
|
||||
res.clientScopeId = MarshallUtil.unmarshallString(input);
|
||||
res.realmId = MarshallUtil.unmarshallString(input);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright 2021 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.cache.infinispan.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.cache.infinispan.RealmCacheManager;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
|
||||
import org.infinispan.commons.marshall.Externalizer;
|
||||
import org.infinispan.commons.marshall.MarshallUtil;
|
||||
import org.infinispan.commons.marshall.SerializeWith;
|
||||
|
||||
@SerializeWith(ClientScopeRemovedEvent.ExternalizerImpl.class)
|
||||
public class ClientScopeRemovedEvent extends InvalidationEvent implements RealmCacheInvalidationEvent {
|
||||
|
||||
private String clientScopeId;
|
||||
private String realmId;
|
||||
|
||||
public static ClientScopeRemovedEvent create(String clientScopeId, String realmId) {
|
||||
ClientScopeRemovedEvent event = new ClientScopeRemovedEvent();
|
||||
event.clientScopeId = clientScopeId;
|
||||
event.realmId = realmId;
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return clientScopeId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ClientScopeRemovedEvent [ clientScopeId=%s, realmId=%s ]", clientScopeId, realmId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInvalidations(RealmCacheManager realmCache, Set<String> invalidations) {
|
||||
realmCache.clientScopeRemoval(realmId, invalidations);
|
||||
}
|
||||
|
||||
public static class ExternalizerImpl implements Externalizer<ClientScopeRemovedEvent> {
|
||||
|
||||
private static final int VERSION_1 = 1;
|
||||
|
||||
@Override
|
||||
public void writeObject(ObjectOutput output, ClientScopeRemovedEvent obj) throws IOException {
|
||||
output.writeByte(VERSION_1);
|
||||
|
||||
MarshallUtil.marshallString(obj.clientScopeId, output);
|
||||
MarshallUtil.marshallString(obj.realmId, output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeRemovedEvent readObject(ObjectInput input) throws IOException, ClassNotFoundException {
|
||||
switch (input.readByte()) {
|
||||
case VERSION_1:
|
||||
return readObjectVersion1(input);
|
||||
default:
|
||||
throw new IOException("Unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
public ClientScopeRemovedEvent readObjectVersion1(ObjectInput input) throws IOException, ClassNotFoundException {
|
||||
ClientScopeRemovedEvent res = new ClientScopeRemovedEvent();
|
||||
res.clientScopeId = MarshallUtil.unmarshallString(input);
|
||||
res.realmId = MarshallUtil.unmarshallString(input);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -364,7 +364,7 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
|
|||
|
||||
private void persist(ClientScopeModel clientScope, boolean defaultScope) {
|
||||
ClientScopeClientMappingEntity entity = new ClientScopeClientMappingEntity();
|
||||
entity.setClientScope(ClientScopeAdapter.toClientScopeEntity(clientScope, em));
|
||||
entity.setClientScopeId(clientScope.getId());
|
||||
entity.setClient(getEntity());
|
||||
entity.setDefaultScope(defaultScope);
|
||||
em.persist(entity);
|
||||
|
@ -375,7 +375,7 @@ public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
|
|||
@Override
|
||||
public void removeClientScope(ClientScopeModel clientScope) {
|
||||
int numRemoved = em.createNamedQuery("deleteClientScopeClientMapping")
|
||||
.setParameter("clientScope", ClientScopeAdapter.toClientScopeEntity(clientScope, em))
|
||||
.setParameter("clientScopeId", clientScope.getId())
|
||||
.setParameter("client", getEntity())
|
||||
.executeUpdate();
|
||||
em.flush();
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright 2021 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.jpa;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.ClientScopeProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
public class JpaClientScopeProviderFactory implements ClientScopeProviderFactory {
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "jpa";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeProvider create(KeycloakSession session) {
|
||||
EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
||||
return new JpaRealmProvider(session, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,22 @@
|
|||
|
||||
package org.keycloak.models.jpa;
|
||||
|
||||
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
|
||||
import static org.keycloak.utils.StreamsUtil.closing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaDelete;
|
||||
import javax.persistence.criteria.Root;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
|
@ -25,10 +41,12 @@ import org.keycloak.models.ClientInitialAccessModel;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientProvider;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.GroupProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
|
@ -43,33 +61,11 @@ import org.keycloak.models.jpa.entities.RealmLocalizationTextsEntity;
|
|||
import org.keycloak.models.jpa.entities.RoleEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaDelete;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.models.ModelException;
|
||||
|
||||
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
|
||||
import static org.keycloak.utils.StreamsUtil.closing;
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupProvider, RoleProvider {
|
||||
public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientScopeProvider, GroupProvider, RoleProvider {
|
||||
protected static final Logger logger = Logger.getLogger(JpaRealmProvider.class);
|
||||
private final KeycloakSession session;
|
||||
protected EntityManager em;
|
||||
|
@ -168,10 +164,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro
|
|||
num = em.createNamedQuery("deleteDefaultClientScopeRealmMappingByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
|
||||
for (ClientScopeEntity a : new LinkedList<>(realm.getClientScopes())) {
|
||||
adapter.removeClientScope(a.getId());
|
||||
}
|
||||
|
||||
session.clientScopes().removeClientScopes(adapter);
|
||||
session.roles().removeRoles(adapter);
|
||||
|
||||
adapter.getTopLevelGroupsStream().forEach(adapter::removeGroup);
|
||||
|
@ -737,15 +730,66 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, GroupPro
|
|||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel getClientScopeById(String id, RealmModel realm) {
|
||||
ClientScopeEntity app = em.find(ClientScopeEntity.class, id);
|
||||
public ClientScopeModel getClientScopeById(RealmModel realm, String id) {
|
||||
ClientScopeEntity clientScope = em.find(ClientScopeEntity.class, id);
|
||||
|
||||
// Check if application belongs to this realm
|
||||
if (app == null || !realm.getId().equals(app.getRealm().getId())) return null;
|
||||
ClientScopeAdapter adapter = new ClientScopeAdapter(realm, em, session, app);
|
||||
// Check if client scope belongs to this realm
|
||||
if (clientScope == null || !realm.getId().equals(clientScope.getRealm().getId())) return null;
|
||||
ClientScopeAdapter adapter = new ClientScopeAdapter(realm, em, session, clientScope);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ClientScopeModel> getClientScopesStream(RealmModel realm) {
|
||||
TypedQuery<String> query = em.createNamedQuery("getClientScopeIds", String.class);
|
||||
query.setParameter("realm", realm.getId());
|
||||
Stream<String> scopes = query.getResultStream();
|
||||
|
||||
return closing(scopes.map(realm::getClientScopeById));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addClientScope(RealmModel realm, String id, String name) {
|
||||
if (id == null) {
|
||||
id = KeycloakModelUtils.generateId();
|
||||
}
|
||||
ClientScopeEntity entity = new ClientScopeEntity();
|
||||
entity.setId(id);
|
||||
name = KeycloakModelUtils.convertClientScopeName(name);
|
||||
entity.setName(name);
|
||||
RealmEntity ref = em.getReference(RealmEntity.class, realm.getId());
|
||||
entity.setRealm(ref);
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
return new ClientScopeAdapter(realm, em, session, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeClientScope(RealmModel realm, String id) {
|
||||
if (id == null) return false;
|
||||
ClientScopeModel clientScope = getClientScopeById(realm, id);
|
||||
if (clientScope == null) return false;
|
||||
|
||||
if (KeycloakModelUtils.isClientScopeUsed(realm, clientScope)) {
|
||||
throw new ModelException("Cannot remove client scope, it is currently in use");
|
||||
}
|
||||
|
||||
session.users().preRemove(clientScope);
|
||||
realm.removeDefaultClientScope(clientScope);
|
||||
ClientScopeEntity clientScopeEntity = em.find(ClientScopeEntity.class, id, LockModeType.PESSIMISTIC_WRITE);
|
||||
|
||||
em.createNamedQuery("deleteClientScopeRoleMappingByClientScope").setParameter("clientScope", clientScopeEntity).executeUpdate();
|
||||
em.remove(clientScopeEntity);
|
||||
em.flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScopes(RealmModel realm) {
|
||||
// No need to go through cache. Client scopes were already invalidated
|
||||
realm.getClientScopesStream().map(ClientScopeModel::getId).forEach(id -> this.removeClientScope(realm, id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> searchForGroupByNameStream(RealmModel realm, String search, Integer first, Integer max) {
|
||||
TypedQuery<String> query = em.createNamedQuery("getGroupIdsByNameContaining", String.class)
|
||||
|
|
|
@ -1958,72 +1958,33 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
|
||||
@Override
|
||||
public Stream<ClientScopeModel> getClientScopesStream() {
|
||||
return realm.getClientScopes().stream().map(ClientScopeEntity::getId).map(this::getClientScopeById);
|
||||
return session.clientScopes().getClientScopesStream(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addClientScope(String name) {
|
||||
return this.addClientScope(KeycloakModelUtils.generateId(), name);
|
||||
return session.clientScopes().addClientScope(this, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addClientScope(String id, String name) {
|
||||
ClientScopeEntity entity = new ClientScopeEntity();
|
||||
entity.setId(id);
|
||||
name = KeycloakModelUtils.convertClientScopeName(name);
|
||||
entity.setName(name);
|
||||
entity.setRealm(realm);
|
||||
realm.getClientScopes().add(entity);
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
final ClientScopeModel resource = new ClientScopeAdapter(this, em, session, entity);
|
||||
em.flush();
|
||||
return resource;
|
||||
return session.clientScopes().addClientScope(this, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeClientScope(String id) {
|
||||
if (id == null) return false;
|
||||
ClientScopeModel clientScope = getClientScopeById(id);
|
||||
if (clientScope == null) return false;
|
||||
if (KeycloakModelUtils.isClientScopeUsed(this, clientScope)) {
|
||||
throw new ModelException("Cannot remove client scope, it is currently in use");
|
||||
}
|
||||
|
||||
ClientScopeEntity clientScopeEntity = null;
|
||||
Iterator<ClientScopeEntity> it = realm.getClientScopes().iterator();
|
||||
while (it.hasNext()) {
|
||||
ClientScopeEntity ae = it.next();
|
||||
if (ae.getId().equals(id)) {
|
||||
clientScopeEntity = ae;
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (clientScope == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
session.users().preRemove(clientScope);
|
||||
|
||||
em.createNamedQuery("deleteClientScopeRoleMappingByClientScope").setParameter("clientScope", clientScopeEntity).executeUpdate();
|
||||
em.flush();
|
||||
em.remove(clientScopeEntity);
|
||||
em.flush();
|
||||
|
||||
|
||||
return true;
|
||||
return session.clientScopes().removeClientScope(this, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel getClientScopeById(String id) {
|
||||
return session.realms().getClientScopeById(id, this);
|
||||
return session.clientScopes().getClientScopeById(this, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDefaultClientScope(ClientScopeModel clientScope, boolean defaultScope) {
|
||||
DefaultClientScopeRealmMappingEntity entity = new DefaultClientScopeRealmMappingEntity();
|
||||
entity.setClientScope(ClientScopeAdapter.toClientScopeEntity(clientScope, em));
|
||||
entity.setClientScopeId(clientScope.getId());
|
||||
entity.setRealm(getEntity());
|
||||
entity.setDefaultScope(defaultScope);
|
||||
em.persist(entity);
|
||||
|
@ -2034,7 +1995,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
@Override
|
||||
public void removeDefaultClientScope(ClientScopeModel clientScope) {
|
||||
int numRemoved = em.createNamedQuery("deleteDefaultClientScopeRealmMapping")
|
||||
.setParameter("clientScope", ClientScopeAdapter.toClientScopeEntity(clientScope, em))
|
||||
.setParameter("clientScopeId", clientScope.getId())
|
||||
.setParameter("realm", getEntity())
|
||||
.executeUpdate();
|
||||
em.flush();
|
||||
|
|
|
@ -36,8 +36,8 @@ import javax.persistence.Table;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="clientScopeClientMappingIdsByClient", query="select m.clientScope.id from ClientScopeClientMappingEntity m where m.client = :client and m.defaultScope = :defaultScope"),
|
||||
@NamedQuery(name="deleteClientScopeClientMapping", query="delete from ClientScopeClientMappingEntity where client = :client and clientScope = :clientScope"),
|
||||
@NamedQuery(name="clientScopeClientMappingIdsByClient", query="select m.clientScopeId from ClientScopeClientMappingEntity m where m.client = :client and m.defaultScope = :defaultScope"),
|
||||
@NamedQuery(name="deleteClientScopeClientMapping", query="delete from ClientScopeClientMappingEntity where client = :client and clientScopeId = :clientScopeId"),
|
||||
@NamedQuery(name="deleteClientScopeClientMappingByClient", query="delete from ClientScopeClientMappingEntity where client = :client")
|
||||
})
|
||||
@Entity
|
||||
|
@ -46,9 +46,8 @@ import javax.persistence.Table;
|
|||
public class ClientScopeClientMappingEntity {
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name = "SCOPE_ID")
|
||||
protected ClientScopeEntity clientScope;
|
||||
@Column(name = "SCOPE_ID")
|
||||
protected String clientScopeId;
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
|
@ -58,12 +57,12 @@ public class ClientScopeClientMappingEntity {
|
|||
@Column(name="DEFAULT_SCOPE")
|
||||
protected boolean defaultScope;
|
||||
|
||||
public ClientScopeEntity getClientScope() {
|
||||
return clientScope;
|
||||
public String getClientScopeId() {
|
||||
return clientScopeId;
|
||||
}
|
||||
|
||||
public void setClientScope(ClientScopeEntity clientScope) {
|
||||
this.clientScope = clientScope;
|
||||
public void setClientScopeId(String clientScopeId) {
|
||||
this.clientScopeId = clientScopeId;
|
||||
}
|
||||
|
||||
public ClientEntity getClient() {
|
||||
|
@ -84,20 +83,20 @@ public class ClientScopeClientMappingEntity {
|
|||
|
||||
public static class Key implements Serializable {
|
||||
|
||||
protected ClientScopeEntity clientScope;
|
||||
protected String clientScopeId;
|
||||
|
||||
protected ClientEntity client;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(ClientScopeEntity clientScope, ClientEntity client) {
|
||||
this.clientScope = clientScope;
|
||||
public Key(String clientScopeId, ClientEntity client) {
|
||||
this.clientScopeId = clientScopeId;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public ClientScopeEntity getClientScope() {
|
||||
return clientScope;
|
||||
public String getClientScopeId() {
|
||||
return clientScopeId;
|
||||
}
|
||||
|
||||
public ClientEntity getClient() {
|
||||
|
@ -111,7 +110,7 @@ public class ClientScopeClientMappingEntity {
|
|||
|
||||
ClientScopeClientMappingEntity.Key key = (ClientScopeClientMappingEntity.Key) o;
|
||||
|
||||
if (clientScope != null ? !clientScope.getId().equals(key.clientScope != null ? key.clientScope.getId() : null) : key.clientScope != null) return false;
|
||||
if (clientScopeId != null ? !clientScopeId.equals(key.getClientScopeId() != null ? key.getClientScopeId() : null) : key.getClientScopeId() != null) return false;
|
||||
if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
|
||||
|
||||
return true;
|
||||
|
@ -119,7 +118,7 @@ public class ClientScopeClientMappingEntity {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientScope != null ? clientScope.getId().hashCode() : 0;
|
||||
int result = clientScopeId != null ? clientScopeId.hashCode() : 0;
|
||||
result = 31 * result + (client != null ? client.getId().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
@ -133,7 +132,7 @@ public class ClientScopeClientMappingEntity {
|
|||
|
||||
ClientScopeClientMappingEntity key = (ClientScopeClientMappingEntity) o;
|
||||
|
||||
if (clientScope != null ? !clientScope.getId().equals(key.clientScope != null ? key.clientScope.getId() : null) : key.clientScope != null) return false;
|
||||
if (clientScopeId != null ? !clientScopeId.equals(key.getClientScopeId() != null ? key.getClientScopeId() : null) : key.getClientScopeId() != null) return false;
|
||||
if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
|
||||
|
||||
return true;
|
||||
|
@ -141,7 +140,7 @@ public class ClientScopeClientMappingEntity {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientScope != null ? clientScope.getId().hashCode() : 0;
|
||||
int result = clientScopeId != null ? clientScopeId.hashCode() : 0;
|
||||
result = 31 * result + (client != null ? client.getId().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
|
@ -33,8 +32,9 @@ import javax.persistence.Entity;
|
|||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
@ -47,6 +47,9 @@ import org.hibernate.annotations.Nationalized;
|
|||
*/
|
||||
@Entity
|
||||
@Table(name="CLIENT_SCOPE", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "NAME"})})
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getClientScopeIds", query="select scope.id from ClientScopeEntity scope where scope.realm.id = :realm")
|
||||
})
|
||||
public class ClientScopeEntity {
|
||||
|
||||
@Id
|
||||
|
|
|
@ -36,8 +36,8 @@ import javax.persistence.Table;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="defaultClientScopeRealmMappingIdsByRealm", query="select m.clientScope.id from DefaultClientScopeRealmMappingEntity m where m.realm = :realm and m.defaultScope = :defaultScope"),
|
||||
@NamedQuery(name="deleteDefaultClientScopeRealmMapping", query="delete from DefaultClientScopeRealmMappingEntity where realm = :realm and clientScope = :clientScope"),
|
||||
@NamedQuery(name="defaultClientScopeRealmMappingIdsByRealm", query="select m.clientScopeId from DefaultClientScopeRealmMappingEntity m where m.realm = :realm and m.defaultScope = :defaultScope"),
|
||||
@NamedQuery(name="deleteDefaultClientScopeRealmMapping", query="delete from DefaultClientScopeRealmMappingEntity where realm = :realm and clientScopeId = :clientScopeId"),
|
||||
@NamedQuery(name="deleteDefaultClientScopeRealmMappingByRealm", query="delete from DefaultClientScopeRealmMappingEntity where realm = :realm")
|
||||
})
|
||||
@Entity
|
||||
|
@ -46,9 +46,8 @@ import javax.persistence.Table;
|
|||
public class DefaultClientScopeRealmMappingEntity {
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
@JoinColumn(name = "SCOPE_ID")
|
||||
protected ClientScopeEntity clientScope;
|
||||
@Column(name = "SCOPE_ID")
|
||||
protected String clientScopeId;
|
||||
|
||||
@Id
|
||||
@ManyToOne(fetch= FetchType.LAZY)
|
||||
|
@ -58,12 +57,12 @@ public class DefaultClientScopeRealmMappingEntity {
|
|||
@Column(name="DEFAULT_SCOPE")
|
||||
protected boolean defaultScope;
|
||||
|
||||
public ClientScopeEntity getClientScope() {
|
||||
return clientScope;
|
||||
public String getClientScopeId() {
|
||||
return clientScopeId;
|
||||
}
|
||||
|
||||
public void setClientScope(ClientScopeEntity clientScope) {
|
||||
this.clientScope = clientScope;
|
||||
public void setClientScopeId(String clientScopeId) {
|
||||
this.clientScopeId = clientScopeId;
|
||||
}
|
||||
|
||||
public RealmEntity getRealm() {
|
||||
|
@ -84,20 +83,20 @@ public class DefaultClientScopeRealmMappingEntity {
|
|||
|
||||
public static class Key implements Serializable {
|
||||
|
||||
protected ClientScopeEntity clientScope;
|
||||
protected String clientScopeId;
|
||||
|
||||
protected RealmEntity realm;
|
||||
|
||||
public Key() {
|
||||
}
|
||||
|
||||
public Key(ClientScopeEntity clientScope, RealmEntity realm) {
|
||||
this.clientScope = clientScope;
|
||||
public Key(String clientScopeId, RealmEntity realm) {
|
||||
this.clientScopeId = clientScopeId;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public ClientScopeEntity getClientScope() {
|
||||
return clientScope;
|
||||
public String getClientScopeId() {
|
||||
return clientScopeId;
|
||||
}
|
||||
|
||||
public RealmEntity getRealm() {
|
||||
|
@ -111,7 +110,7 @@ public class DefaultClientScopeRealmMappingEntity {
|
|||
|
||||
DefaultClientScopeRealmMappingEntity.Key key = (DefaultClientScopeRealmMappingEntity.Key) o;
|
||||
|
||||
if (clientScope != null ? !clientScope.getId().equals(key.clientScope != null ? key.clientScope.getId() : null) : key.clientScope != null) return false;
|
||||
if (clientScopeId != null ? !clientScopeId.equals(key.getClientScopeId() != null ? key.getClientScopeId() : null) : key.getClientScopeId() != null) return false;
|
||||
if (realm != null ? !realm.getId().equals(key.realm != null ? key.realm.getId() : null) : key.realm != null) return false;
|
||||
|
||||
return true;
|
||||
|
@ -119,7 +118,7 @@ public class DefaultClientScopeRealmMappingEntity {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientScope != null ? clientScope.getId().hashCode() : 0;
|
||||
int result = clientScopeId != null ? clientScopeId.hashCode() : 0;
|
||||
result = 31 * result + (realm != null ? realm.getId().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
@ -133,7 +132,7 @@ public class DefaultClientScopeRealmMappingEntity {
|
|||
|
||||
DefaultClientScopeRealmMappingEntity key = (DefaultClientScopeRealmMappingEntity) o;
|
||||
|
||||
if (clientScope != null ? !clientScope.getId().equals(key.clientScope != null ? key.clientScope.getId() : null) : key.clientScope != null) return false;
|
||||
if (clientScopeId != null ? !clientScopeId.equals(key.getClientScopeId() != null ? key.getClientScopeId() : null) : key.getClientScopeId() != null) return false;
|
||||
if (realm != null ? !realm.getId().equals(key.realm != null ? key.realm.getId() : null) : key.realm != null) return false;
|
||||
|
||||
return true;
|
||||
|
@ -141,7 +140,7 @@ public class DefaultClientScopeRealmMappingEntity {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = clientScope != null ? clientScope.getId().hashCode() : 0;
|
||||
int result = clientScopeId != null ? clientScopeId.hashCode() : 0;
|
||||
result = 31 * result + (realm != null ? realm.getId().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -148,6 +148,7 @@ public class RealmEntity {
|
|||
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
Collection<UserFederationMapperEntity> userFederationMappers;
|
||||
|
||||
@Deprecated
|
||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
Collection<ClientScopeEntity> clientScopes;
|
||||
|
||||
|
@ -813,6 +814,7 @@ public class RealmEntity {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Collection<ClientScopeEntity> getClientScopes() {
|
||||
if (clientScopes == null) {
|
||||
clientScopes = new LinkedList<>();
|
||||
|
@ -820,6 +822,7 @@ public class RealmEntity {
|
|||
return clientScopes;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setClientScopes(Collection<ClientScopeEntity> clientScopes) {
|
||||
this.clientScopes = clientScopes;
|
||||
}
|
||||
|
|
|
@ -38,4 +38,8 @@
|
|||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="keycloak" id="map-remove-ri-13.0.0">
|
||||
<dropForeignKeyConstraint baseTableName="DEFAULT_CLIENT_SCOPE" constraintName="FK_R_DEF_CLI_SCOPE_SCOPE"/>
|
||||
<dropForeignKeyConstraint baseTableName="CLIENT_SCOPE_CLIENT" constraintName="FK_C_CLI_SCOPE_SCOPE"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Copyright 2021 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.
|
||||
#
|
||||
|
||||
org.keycloak.models.jpa.JpaClientScopeProviderFactory
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright 2021 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.map.clientscope;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
|
||||
public abstract class AbstractClientScopeEntity<K> implements AbstractEntity<K> {
|
||||
|
||||
private final K id;
|
||||
private final String realmId;
|
||||
|
||||
private String name;
|
||||
private String protocol;
|
||||
private String description;
|
||||
|
||||
private final Set<String> scopeMappings = new LinkedHashSet<>();
|
||||
private final Map<String, ProtocolMapperModel> protocolMappers = new HashMap<>();
|
||||
private final Map<String, String> attributes = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Flag signalizing that any of the setters has been meaningfully used.
|
||||
*/
|
||||
protected boolean updated;
|
||||
|
||||
protected AbstractClientScopeEntity() {
|
||||
this.id = null;
|
||||
this.realmId = null;
|
||||
}
|
||||
|
||||
public AbstractClientScopeEntity(K id, String realmId) {
|
||||
Objects.requireNonNull(id, "id");
|
||||
Objects.requireNonNull(realmId, "realmId");
|
||||
|
||||
this.id = id;
|
||||
this.realmId = realmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdated() {
|
||||
return this.updated;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.updated |= ! Objects.equals(this.name, name);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.updated |= ! Objects.equals(this.description, description);
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.updated |= ! Objects.equals(this.protocol, protocol);
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, String> attributes) {
|
||||
this.updated |= ! Objects.equals(this.attributes, attributes);
|
||||
this.attributes.clear();
|
||||
this.attributes.putAll(attributes);
|
||||
}
|
||||
|
||||
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
|
||||
Objects.requireNonNull(model.getId(), "protocolMapper.id");
|
||||
updated = true;
|
||||
this.protocolMappers.put(model.getId(), model);
|
||||
return model;
|
||||
}
|
||||
|
||||
public Stream<ProtocolMapperModel> getProtocolMappers() {
|
||||
return protocolMappers.values().stream();
|
||||
}
|
||||
|
||||
public void updateProtocolMapper(String id, ProtocolMapperModel mapping) {
|
||||
updated = true;
|
||||
protocolMappers.put(id, mapping);
|
||||
}
|
||||
|
||||
public void removeProtocolMapper(String id) {
|
||||
updated |= protocolMappers.remove(id) != null;
|
||||
}
|
||||
|
||||
public void setProtocolMappers(Collection<ProtocolMapperModel> protocolMappers) {
|
||||
this.updated |= ! Objects.equals(this.protocolMappers, protocolMappers);
|
||||
this.protocolMappers.clear();
|
||||
this.protocolMappers.putAll(protocolMappers.stream().collect(Collectors.toMap(ProtocolMapperModel::getId, Function.identity())));
|
||||
}
|
||||
|
||||
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||
return id == null ? null : protocolMappers.get(id);
|
||||
}
|
||||
|
||||
public void setAttribute(String name, String value) {
|
||||
this.updated = true;
|
||||
this.attributes.put(name, value);
|
||||
}
|
||||
|
||||
public void removeAttribute(String name) {
|
||||
this.updated |= this.attributes.remove(name) != null;
|
||||
}
|
||||
|
||||
public String getAttribute(String name) {
|
||||
return this.attributes.get(name);
|
||||
}
|
||||
|
||||
public String getRealmId() {
|
||||
return this.realmId;
|
||||
}
|
||||
|
||||
public Stream<String> getScopeMappings() {
|
||||
return scopeMappings.stream();
|
||||
}
|
||||
|
||||
public void addScopeMapping(String id) {
|
||||
if (id != null) {
|
||||
updated = true;
|
||||
scopeMappings.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteScopeMapping(String id) {
|
||||
updated |= scopeMappings.remove(id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2021 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.map.clientscope;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
|
||||
public abstract class AbstractClientScopeModel<E extends AbstractEntity> implements ClientScopeModel {
|
||||
|
||||
protected final KeycloakSession session;
|
||||
protected final RealmModel realm;
|
||||
protected final E entity;
|
||||
|
||||
public AbstractClientScopeModel(KeycloakSession session, RealmModel realm, E entity) {
|
||||
Objects.requireNonNull(entity, "entity");
|
||||
Objects.requireNonNull(realm, "realm");
|
||||
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ClientScopeModel)) return false;
|
||||
|
||||
ClientScopeModel that = (ClientScopeModel) o;
|
||||
return Objects.equals(that.getId(), getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Copyright 2021 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.map.clientscope;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
|
||||
public class MapClientScopeAdapter extends AbstractClientScopeModel<MapClientScopeEntity> implements ClientScopeModel {
|
||||
|
||||
public MapClientScopeAdapter(KeycloakSession session, RealmModel realm, MapClientScopeEntity entity) {
|
||||
super(session, realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return entity.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
entity.setName(KeycloakModelUtils.convertClientScopeName(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return entity.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
entity.setDescription(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return entity.getProtocol();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocol(String protocol) {
|
||||
entity.setProtocol(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
entity.setAttribute(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
entity.removeAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
return entity.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return entity.getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
|
||||
return entity.getProtocolMappers().distinct();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ProtocolMapperModel pm = new ProtocolMapperModel();
|
||||
pm.setId(KeycloakModelUtils.generateId());
|
||||
pm.setName(model.getName());
|
||||
pm.setProtocol(model.getProtocol());
|
||||
pm.setProtocolMapper(model.getProtocolMapper());
|
||||
|
||||
if (model.getConfig() != null) {
|
||||
pm.setConfig(new HashMap<>(model.getConfig()));
|
||||
} else {
|
||||
pm.setConfig(new HashMap<>());
|
||||
}
|
||||
|
||||
return entity.addProtocolMapper(pm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProtocolMapper(ProtocolMapperModel mapping) {
|
||||
final String id = mapping == null ? null : mapping.getId();
|
||||
if (id != null) {
|
||||
entity.removeProtocolMapper(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProtocolMapper(ProtocolMapperModel mapping) {
|
||||
final String id = mapping == null ? null : mapping.getId();
|
||||
if (id != null) {
|
||||
entity.updateProtocolMapper(id, mapping);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||
return entity.getProtocolMapperById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
|
||||
return entity.getProtocolMappers()
|
||||
.filter(pm -> Objects.equals(pm.getProtocol(), protocol) && Objects.equals(pm.getName(), name))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getScopeMappingsStream() {
|
||||
return this.entity.getScopeMappings()
|
||||
.map(realm::getRoleById)
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRealmScopeMappingsStream() {
|
||||
return getScopeMappingsStream().filter(r -> RoleUtils.isRealmRole(r, realm));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(RoleModel role) {
|
||||
final String id = role == null ? null : role.getId();
|
||||
if (id != null) {
|
||||
this.entity.addScopeMapping(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteScopeMapping(RoleModel role) {
|
||||
final String id = role == null ? null : role.getId();
|
||||
if (id != null) {
|
||||
this.entity.deleteScopeMapping(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScope(RoleModel role) {
|
||||
return RoleUtils.hasRole(getScopeMappingsStream(), role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s@%08x", getId(), System.identityHashCode(this));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2021 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.map.clientscope;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class MapClientScopeEntity extends AbstractClientScopeEntity<UUID> {
|
||||
|
||||
private MapClientScopeEntity() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MapClientScopeEntity(UUID id, String realmId) {
|
||||
super(id, realmId);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright 2021 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.map.clientscope;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.jboss.logging.Logger;
|
||||
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||
import org.keycloak.models.ClientScopeModel.SearchableFields;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.map.common.Serialization;
|
||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||
import org.keycloak.models.map.storage.MapStorage;
|
||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
public class MapClientScopeProvider implements ClientScopeProvider {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(MapClientScopeProvider.class);
|
||||
private static final Predicate<MapClientScopeEntity> ALWAYS_FALSE = c -> { return false; };
|
||||
private final KeycloakSession session;
|
||||
private final MapKeycloakTransaction<UUID, MapClientScopeEntity, ClientScopeModel> tx;
|
||||
private final MapStorage<UUID, MapClientScopeEntity, ClientScopeModel> clientScopeStore;
|
||||
|
||||
private static final Comparator<MapClientScopeEntity> COMPARE_BY_NAME = Comparator.comparing(MapClientScopeEntity::getName);
|
||||
|
||||
public MapClientScopeProvider(KeycloakSession session, MapStorage<UUID, MapClientScopeEntity, ClientScopeModel> clientScopeStore) {
|
||||
this.session = session;
|
||||
this.clientScopeStore = clientScopeStore;
|
||||
this.tx = clientScopeStore.createTransaction();
|
||||
session.getTransactionManager().enlist(tx);
|
||||
}
|
||||
|
||||
private MapClientScopeEntity registerEntityForChanges(MapClientScopeEntity origEntity) {
|
||||
final MapClientScopeEntity res = tx.read(origEntity.getId(), id -> Serialization.from(origEntity));
|
||||
tx.updateIfChanged(origEntity.getId(), res, MapClientScopeEntity::isUpdated);
|
||||
return res;
|
||||
}
|
||||
|
||||
private Function<MapClientScopeEntity, ClientScopeModel> entityToAdapterFunc(RealmModel realm) {
|
||||
// Clone entity before returning back, to avoid giving away a reference to the live object to the caller
|
||||
|
||||
return origEntity -> new MapClientScopeAdapter(session, realm, registerEntityForChanges(origEntity));
|
||||
}
|
||||
|
||||
private Predicate<MapClientScopeEntity> entityRealmFilter(RealmModel realm) {
|
||||
if (realm == null || realm.getId() == null) {
|
||||
return MapClientScopeProvider.ALWAYS_FALSE;
|
||||
}
|
||||
String realmId = realm.getId();
|
||||
return entity -> Objects.equals(realmId, entity.getRealmId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ClientScopeModel> getClientScopesStream(RealmModel realm) {
|
||||
ModelCriteriaBuilder<ClientScopeModel> mcb = clientScopeStore.createCriteriaBuilder()
|
||||
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
|
||||
|
||||
return tx.getUpdatedNotRemoved(mcb)
|
||||
.sorted(COMPARE_BY_NAME)
|
||||
.map(entityToAdapterFunc(realm));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addClientScope(RealmModel realm, String id, String name) {
|
||||
// Check Db constraint: @UniqueConstraint(columnNames = {"REALM_ID", "NAME"})
|
||||
ModelCriteriaBuilder<ClientScopeModel> mcb = clientScopeStore.createCriteriaBuilder()
|
||||
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
|
||||
.compare(SearchableFields.NAME, Operator.EQ, name);
|
||||
|
||||
if (tx.getCount(mcb) > 0) {
|
||||
throw new ModelDuplicateException("Client scope with name '" + name + "' in realm " + realm.getName());
|
||||
}
|
||||
|
||||
final UUID entityId = id == null ? UUID.randomUUID() : UUID.fromString(id);
|
||||
|
||||
LOG.tracef("addClientScope(%s, %s, %s)%s", realm, id, name, getShortStackTrace());
|
||||
|
||||
MapClientScopeEntity entity = new MapClientScopeEntity(entityId, realm.getId());
|
||||
entity.setName(KeycloakModelUtils.convertClientScopeName(name));
|
||||
if (tx.read(entity.getId()) != null) {
|
||||
throw new ModelDuplicateException("Client scope exists: " + id);
|
||||
}
|
||||
tx.create(entity.getId(), entity);
|
||||
return entityToAdapterFunc(realm).apply(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeClientScope(RealmModel realm, String id) {
|
||||
if (id == null) return false;
|
||||
ClientScopeModel clientScope = getClientScopeById(realm, id);
|
||||
if (clientScope == null) return false;
|
||||
|
||||
if (KeycloakModelUtils.isClientScopeUsed(realm, clientScope)) {
|
||||
throw new ModelException("Cannot remove client scope, it is currently in use");
|
||||
}
|
||||
|
||||
session.users().preRemove(clientScope);
|
||||
realm.removeDefaultClientScope(clientScope);
|
||||
|
||||
tx.delete(UUID.fromString(id));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScopes(RealmModel realm) {
|
||||
LOG.tracef("removeClients(%s)%s", realm, getShortStackTrace());
|
||||
|
||||
getClientScopesStream(realm)
|
||||
.map(ClientScopeModel::getId)
|
||||
.collect(Collectors.toSet()) // This is necessary to read out all the client IDs before removing the clients
|
||||
.forEach(id -> removeClientScope(realm, id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel getClientScopeById(RealmModel realm, String id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LOG.tracef("getClientScopeById(%s, %s)%s", realm, id, getShortStackTrace());
|
||||
|
||||
UUID uuid;
|
||||
try {
|
||||
uuid = UUID.fromString(id);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MapClientScopeEntity entity = tx.read(uuid);
|
||||
return (entity == null || ! entityRealmFilter(realm).test(entity))
|
||||
? null
|
||||
: entityToAdapterFunc(realm).apply(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2021 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.map.clientscope;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.ClientScopeProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||
import org.keycloak.models.map.storage.MapStorage;
|
||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||
|
||||
public class MapClientScopeProviderFactory extends AbstractMapProviderFactory<ClientScopeProvider> implements ClientScopeProviderFactory {
|
||||
|
||||
private MapStorage<UUID, MapClientScopeEntity, ClientScopeModel> store;
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
|
||||
this.store = sp.getStorage("clientscope", UUID.class, MapClientScopeEntity.class, ClientScopeModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeProvider create(KeycloakSession session) {
|
||||
return new MapClientScopeProvider(session, store);
|
||||
}
|
||||
}
|
|
@ -17,11 +17,13 @@
|
|||
package org.keycloak.models.map.storage;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.map.authSession.AbstractRootAuthenticationSessionEntity;
|
||||
import org.keycloak.models.map.client.AbstractClientEntity;
|
||||
import org.keycloak.models.map.clientscope.AbstractClientScopeEntity;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.group.AbstractGroupEntity;
|
||||
import org.keycloak.models.map.role.AbstractRoleEntity;
|
||||
|
@ -48,6 +50,7 @@ import java.util.function.Predicate;
|
|||
public class MapFieldPredicates {
|
||||
|
||||
public static final Map<SearchableModelField<ClientModel>, UpdatePredicatesFunc<Object, AbstractClientEntity<Object>, ClientModel>> CLIENT_PREDICATES = basePredicates(ClientModel.SearchableFields.ID);
|
||||
public static final Map<SearchableModelField<ClientScopeModel>, UpdatePredicatesFunc<Object, AbstractClientScopeEntity<Object>, ClientScopeModel>> CLIENT_SCOPE_PREDICATES = basePredicates(ClientScopeModel.SearchableFields.ID);
|
||||
public static final Map<SearchableModelField<GroupModel>, UpdatePredicatesFunc<Object, AbstractGroupEntity<Object>, GroupModel>> GROUP_PREDICATES = basePredicates(GroupModel.SearchableFields.ID);
|
||||
public static final Map<SearchableModelField<RoleModel>, UpdatePredicatesFunc<Object, AbstractRoleEntity<Object>, RoleModel>> ROLE_PREDICATES = basePredicates(RoleModel.SearchableFields.ID);
|
||||
public static final Map<SearchableModelField<UserModel>, UpdatePredicatesFunc<Object, AbstractUserEntity<Object>, UserModel>> USER_PREDICATES = basePredicates(UserModel.SearchableFields.ID);
|
||||
|
@ -60,6 +63,9 @@ public class MapFieldPredicates {
|
|||
put(CLIENT_PREDICATES, ClientModel.SearchableFields.REALM_ID, AbstractClientEntity::getRealmId);
|
||||
put(CLIENT_PREDICATES, ClientModel.SearchableFields.CLIENT_ID, AbstractClientEntity::getClientId);
|
||||
|
||||
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.REALM_ID, AbstractClientScopeEntity::getRealmId);
|
||||
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.NAME, AbstractClientScopeEntity::getName);
|
||||
|
||||
put(GROUP_PREDICATES, GroupModel.SearchableFields.REALM_ID, AbstractGroupEntity::getRealmId);
|
||||
put(GROUP_PREDICATES, GroupModel.SearchableFields.NAME, AbstractGroupEntity::getName);
|
||||
put(GROUP_PREDICATES, GroupModel.SearchableFields.PARENT_ID, AbstractGroupEntity::getParentId);
|
||||
|
@ -95,6 +101,7 @@ public class MapFieldPredicates {
|
|||
|
||||
static {
|
||||
PREDICATES.put(ClientModel.class, CLIENT_PREDICATES);
|
||||
PREDICATES.put(ClientScopeModel.class, CLIENT_SCOPE_PREDICATES);
|
||||
PREDICATES.put(RoleModel.class, ROLE_PREDICATES);
|
||||
PREDICATES.put(GroupModel.class, GROUP_PREDICATES);
|
||||
PREDICATES.put(UserModel.class, USER_PREDICATES);
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Copyright 2021 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.
|
||||
#
|
||||
|
||||
org.keycloak.models.map.clientscope.MapClientScopeProviderFactory
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2021 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.keycloak.provider.ProviderFactory;
|
||||
|
||||
public interface ClientScopeProviderFactory extends ProviderFactory<ClientScopeProvider> {
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2021 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.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
public class ClientScopeSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "clientScope";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return ClientScopeProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return ClientScopeProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.models.cache;
|
||||
|
||||
import org.keycloak.models.ClientProvider;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.GroupProvider;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.models.RoleProvider;
|
||||
|
@ -26,14 +27,14 @@ import org.keycloak.models.RoleProvider;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface CacheRealmProvider extends RealmProvider, ClientProvider, GroupProvider, RoleProvider {
|
||||
public interface CacheRealmProvider extends RealmProvider, ClientProvider, ClientScopeProvider, GroupProvider, RoleProvider {
|
||||
void clear();
|
||||
RealmProvider getRealmDelegate();
|
||||
|
||||
void registerRealmInvalidation(String id, String name);
|
||||
|
||||
void registerClientInvalidation(String id, String clientId, String realmId);
|
||||
void registerClientScopeInvalidation(String id);
|
||||
void registerClientScopeInvalidation(String id, String realmId);
|
||||
|
||||
void registerRoleInvalidation(String id, String roleName, String roleContainerId);
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2021 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.clientscope;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
public interface ClientScopeStorageProvider extends Provider, ClientScopeLookupProvider {
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright 2021 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.clientscope;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.component.ComponentFactory;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
public interface ClientScopeStorageProviderFactory<T extends ClientScopeStorageProvider> extends ComponentFactory<T, ClientScopeStorageProvider> {
|
||||
|
||||
|
||||
/**
|
||||
* called per Keycloak transaction.
|
||||
*
|
||||
* @param session
|
||||
* @param model
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
T create(KeycloakSession session, ComponentModel model);
|
||||
|
||||
/**
|
||||
* This is the name of the provider.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
String getId();
|
||||
|
||||
@Override
|
||||
default void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getHelpText() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
default List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when ClientScopeStorageProviderFactory is created. This allows you to do initialization of any additional configuration
|
||||
* you need to add.
|
||||
*
|
||||
* @param session
|
||||
* @param realm
|
||||
* @param model
|
||||
*/
|
||||
@Override
|
||||
default void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
|
||||
}
|
||||
|
||||
/**
|
||||
* configuration properties that are common across all ClientScopeStorageProvider implementations
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
default List<ProviderConfigProperty> getCommonProviderConfigProperties() {
|
||||
return ClientScopeStorageProviderSpi.commonConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
default
|
||||
Map<String, Object> getTypeMetadata() {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2021 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.clientscope;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.storage.CacheableStorageProviderModel;
|
||||
|
||||
/**
|
||||
* Stored configuration of a Client scope Storage provider instance.
|
||||
*/
|
||||
public class ClientScopeStorageProviderModel extends CacheableStorageProviderModel {
|
||||
|
||||
public ClientScopeStorageProviderModel() {
|
||||
setProviderType(ClientScopeStorageProvider.class.getName());
|
||||
}
|
||||
|
||||
public ClientScopeStorageProviderModel(ComponentModel copy) {
|
||||
super(copy);
|
||||
}
|
||||
|
||||
private transient Boolean enabled;
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean flag) {
|
||||
enabled = flag;
|
||||
getConfig().putSingle(ENABLED, Boolean.toString(flag));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
if (enabled == null) {
|
||||
String val = getConfig().getFirst(ENABLED);
|
||||
if (val == null) {
|
||||
enabled = true;
|
||||
} else {
|
||||
enabled = Boolean.valueOf(val);
|
||||
}
|
||||
}
|
||||
return enabled;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2021 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.clientscope;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
public class ClientScopeStorageProviderSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "clientscope-storage";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return ClientScopeStorageProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return ClientScopeStorageProviderFactory.class;
|
||||
}
|
||||
|
||||
private static final List<ProviderConfigProperty> commonConfig;
|
||||
|
||||
static {
|
||||
//corresponds to properties defined in CacheableStorageProviderModel and PrioritizedComponentModel
|
||||
List<ProviderConfigProperty> config = ProviderConfigurationBuilder.create()
|
||||
.property()
|
||||
.name("enabled").type(ProviderConfigProperty.BOOLEAN_TYPE).add()
|
||||
.property()
|
||||
.name("priority").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("cachePolicy").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("maxLifespan").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("evictionHour").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("evictionMinute").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("evictionDay").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("cacheInvalidBefore").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.build();
|
||||
commonConfig = Collections.unmodifiableList(config);
|
||||
}
|
||||
|
||||
public static List<ProviderConfigProperty> commonConfig() {
|
||||
return commonConfig;
|
||||
}
|
||||
|
||||
}
|
|
@ -19,6 +19,7 @@ org.keycloak.provider.ExceptionConverterSpi
|
|||
org.keycloak.storage.UserStorageProviderSpi
|
||||
org.keycloak.storage.federated.UserFederatedStorageProviderSpi
|
||||
org.keycloak.models.ClientSpi
|
||||
org.keycloak.models.ClientScopeSpi
|
||||
org.keycloak.models.GroupSpi
|
||||
org.keycloak.models.RealmSpi
|
||||
org.keycloak.models.RoleSpi
|
||||
|
@ -76,6 +77,7 @@ org.keycloak.credential.CredentialSpi
|
|||
org.keycloak.keys.PublicKeyStorageSpi
|
||||
org.keycloak.keys.KeySpi
|
||||
org.keycloak.storage.client.ClientStorageProviderSpi
|
||||
org.keycloak.storage.clientscope.ClientScopeStorageProviderSpi
|
||||
org.keycloak.storage.role.RoleStorageProviderSpi
|
||||
org.keycloak.storage.group.GroupStorageProviderSpi
|
||||
org.keycloak.crypto.SignatureSpi
|
||||
|
|
|
@ -20,12 +20,20 @@ package org.keycloak.models;
|
|||
import java.util.Map;
|
||||
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.storage.SearchableModelField;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface ClientScopeModel extends ProtocolMapperContainerModel, ScopeContainerModel, OrderedModel {
|
||||
|
||||
public static class SearchableFields {
|
||||
public static final SearchableModelField<ClientScopeModel> ID = new SearchableModelField<>("id", String.class);
|
||||
public static final SearchableModelField<ClientScopeModel> REALM_ID = new SearchableModelField<>("realmId", String.class);
|
||||
public static final SearchableModelField<ClientScopeModel> NAME = new SearchableModelField<>("name", String.class);
|
||||
}
|
||||
|
||||
String getId();
|
||||
|
||||
String getName();
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2021 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 java.util.stream.Stream;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.storage.clientscope.ClientScopeLookupProvider;
|
||||
|
||||
/**
|
||||
* Provider of the client scopes records.
|
||||
*/
|
||||
public interface ClientScopeProvider extends Provider, ClientScopeLookupProvider {
|
||||
|
||||
/**
|
||||
* Returns all the client scopes of the given realm as a stream.
|
||||
* @param realm Realm.
|
||||
* @return Stream of the client scopes. Never returns {@code null}.
|
||||
*/
|
||||
Stream<ClientScopeModel> getClientScopesStream(RealmModel realm);
|
||||
|
||||
/**
|
||||
* Creates new client scope with given {@code name} to the given realm.
|
||||
* Spaces in {@code name} will be replaced by underscore so that scope name
|
||||
* can be used as value of scope parameter. The internal ID will be created automatically.
|
||||
* @param realm Realm owning this client scope.
|
||||
* @param name String name of the client scope.
|
||||
* @return Model of the created client scope.
|
||||
* @throws ModelDuplicateException if client scope with given name already exists
|
||||
*/
|
||||
default ClientScopeModel addClientScope(RealmModel realm, String name) {
|
||||
return ClientScopeProvider.this.addClientScope(realm, null, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new client scope with given internal ID and {@code name} to the given realm.
|
||||
* Spaces in {@code name} will be replaced by underscore so that scope name
|
||||
* can be used as value of scope parameter.
|
||||
* @param realm Realm owning this client scope.
|
||||
* @param id Internal ID of the client scope or {@code null} if one is to be created by the underlying store
|
||||
* @param name String name of the client scope.
|
||||
* @return Model of the created client scope.
|
||||
* @throws IllegalArgumentException If {@code id} does not conform
|
||||
* the format understood by the underlying store.
|
||||
* @throws ModelDuplicateException if client scope with given name already exists
|
||||
*/
|
||||
ClientScopeModel addClientScope(RealmModel realm, String id, String name);
|
||||
|
||||
/**
|
||||
* Removes client scope from the given realm.
|
||||
* @param realm Realm.
|
||||
* @param id Internal ID of the client scope
|
||||
* @return {@code true} if the client scope existed and has been removed, {@code false} otherwise.
|
||||
* @throws ModelException if client scope is in use.
|
||||
*/
|
||||
boolean removeClientScope(RealmModel realm, String id);
|
||||
|
||||
/**
|
||||
* Removes all client scopes from the given realm.
|
||||
* @param realm Realm.
|
||||
*/
|
||||
void removeClientScopes(RealmModel realm);
|
||||
}
|
|
@ -115,6 +115,15 @@ public interface KeycloakSession {
|
|||
*/
|
||||
ClientProvider clients();
|
||||
|
||||
/**
|
||||
* Returns a managed provider instance. Will start a provider transaction. This transaction is managed by the KeycloakSession
|
||||
* transaction.
|
||||
*
|
||||
* @return Currently used ClientScopeProvider instance.
|
||||
* @throws IllegalStateException if transaction is not active
|
||||
*/
|
||||
ClientScopeProvider clientScopes();
|
||||
|
||||
/**
|
||||
* Returns a managed group provider instance.
|
||||
*
|
||||
|
@ -162,9 +171,16 @@ public interface KeycloakSession {
|
|||
*/
|
||||
UserProvider users();
|
||||
|
||||
|
||||
/**
|
||||
* @return ClientStorageManager instance
|
||||
*/
|
||||
ClientProvider clientStorageManager();
|
||||
|
||||
/**
|
||||
* @return ClientScopeStorageManager instance
|
||||
*/
|
||||
ClientScopeProvider clientScopeStorageManager();
|
||||
|
||||
/**
|
||||
* @return RoleStorageManager instance
|
||||
*/
|
||||
|
@ -205,6 +221,13 @@ public interface KeycloakSession {
|
|||
*/
|
||||
ClientProvider clientLocalStorage();
|
||||
|
||||
/**
|
||||
* Keycloak specific local storage for client scopes. No cache in front, this api talks directly to database configured for Keycloak
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
ClientScopeProvider clientScopeLocalStorage();
|
||||
|
||||
/**
|
||||
* Keycloak specific local storage for groups. No cache in front, this api talks directly to storage configured for Keycloak
|
||||
*
|
||||
|
|
|
@ -885,15 +885,51 @@ public interface RealmModel extends RoleContainerModel {
|
|||
*/
|
||||
Stream<ClientScopeModel> getClientScopesStream();
|
||||
|
||||
/**
|
||||
* Creates new client scope with the given name. Internal ID is created automatically.
|
||||
* If given name contains spaces, those are replaced by underscores.
|
||||
* @param name {@code String} name of the client scope.
|
||||
* @return Model of the created client scope.
|
||||
* @throws ModelDuplicateException if client scope with same id or name already exists.
|
||||
*/
|
||||
ClientScopeModel addClientScope(String name);
|
||||
|
||||
/**
|
||||
* Creates new client scope with the given internal ID and name.
|
||||
* If given name contains spaces, those are replaced by underscores.
|
||||
* @param id {@code String} id of the client scope.
|
||||
* @param name {@code String} name of the client scope.
|
||||
* @return Model of the created client scope.
|
||||
* @throws ModelDuplicateException if client scope with same id or name already exists.
|
||||
*/
|
||||
ClientScopeModel addClientScope(String id, String name);
|
||||
|
||||
/**
|
||||
* Removes client scope with given {@code id} from this realm.
|
||||
* @param id of the client scope
|
||||
* @return true if the realm contained the scope and the removal was successful, false otherwise
|
||||
*/
|
||||
boolean removeClientScope(String id);
|
||||
|
||||
/**
|
||||
* @param id of the client scope
|
||||
* @return Client scope with the given {@code id}, or {@code null} when the scope does not exist.
|
||||
*/
|
||||
ClientScopeModel getClientScopeById(String id);
|
||||
|
||||
/**
|
||||
* Adds given client scopes among default/optional client scopes of this realm.
|
||||
* The scope will be assigned to each new client.
|
||||
* @param clientScope to be added
|
||||
* @param defaultScope if {@code true} the scope will be added among default client scopes,
|
||||
* if {@code false} it will be added among optional client scopes
|
||||
*/
|
||||
void addDefaultClientScope(ClientScopeModel clientScope, boolean defaultScope);
|
||||
|
||||
/**
|
||||
* Removes given client scope from default or optional client scopes of this realm.
|
||||
* @param clientScope to be removed
|
||||
*/
|
||||
void removeDefaultClientScope(ClientScopeModel clientScope);
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,7 +30,7 @@ import java.util.stream.Stream;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface RealmProvider extends Provider /* TODO: Remove in future version */, ClientProvider, GroupProvider, RoleProvider /* up to here */ {
|
||||
public interface RealmProvider extends Provider /* TODO: Remove in future version */, ClientProvider, ClientScopeProvider, GroupProvider, RoleProvider /* up to here */ {
|
||||
|
||||
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
|
||||
MigrationModel getMigrationModel();
|
||||
|
@ -39,7 +39,16 @@ public interface RealmProvider extends Provider /* TODO: Remove in future versio
|
|||
RealmModel getRealm(String id);
|
||||
RealmModel getRealmByName(String name);
|
||||
|
||||
ClientScopeModel getClientScopeById(String id, RealmModel realm);
|
||||
/**
|
||||
* @deprecated Use the corresponding method from {@link ClientScopeProvider}. */
|
||||
default ClientScopeModel getClientScopeById(String id, RealmModel realm) {
|
||||
return getClientScopeById(realm, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the corresponding method from {@link ClientScopeProvider}. */
|
||||
@Override
|
||||
ClientScopeModel getClientScopeById(RealmModel realm, String id);
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getRealmsStream() getRealmsStream} instead.
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2021 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.clientscope;
|
||||
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
public interface ClientScopeLookupProvider {
|
||||
|
||||
/**
|
||||
* Exact search for a client scope by its internal ID..
|
||||
* @param realm Realm.
|
||||
* @param id Internal ID of the role.
|
||||
* @return Model of the client scope.
|
||||
*/
|
||||
ClientScopeModel getClientScopeById(RealmModel realm, String id);
|
||||
|
||||
}
|
|
@ -22,6 +22,7 @@ import org.keycloak.credential.UserCredentialStoreManager;
|
|||
import org.keycloak.jose.jws.DefaultTokenManager;
|
||||
import org.keycloak.keys.DefaultKeyManager;
|
||||
import org.keycloak.models.ClientProvider;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.GroupProvider;
|
||||
import org.keycloak.models.TokenManager;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
|
@ -43,6 +44,7 @@ import org.keycloak.services.clientpolicy.ClientPolicyManager;
|
|||
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
|
||||
import org.keycloak.sessions.AuthenticationSessionProvider;
|
||||
import org.keycloak.storage.ClientStorageManager;
|
||||
import org.keycloak.storage.ClientScopeStorageManager;
|
||||
import org.keycloak.storage.GroupStorageManager;
|
||||
import org.keycloak.storage.RoleStorageManager;
|
||||
import org.keycloak.storage.UserStorageManager;
|
||||
|
@ -71,10 +73,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
|||
private final Map<String, Object> attributes = new HashMap<>();
|
||||
private RealmProvider model;
|
||||
private ClientProvider clientProvider;
|
||||
private ClientScopeProvider clientScopeProvider;
|
||||
private GroupProvider groupProvider;
|
||||
private RoleProvider roleProvider;
|
||||
private UserStorageManager userStorageManager;
|
||||
private ClientStorageManager clientStorageManager;
|
||||
private ClientScopeStorageManager clientScopeStorageManager;
|
||||
private RoleStorageManager roleStorageManager;
|
||||
private GroupStorageManager groupStorageManager;
|
||||
private UserCredentialStoreManager userCredentialStorageManager;
|
||||
|
@ -118,6 +122,16 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
|||
}
|
||||
}
|
||||
|
||||
private ClientScopeProvider getClientScopeProvider() {
|
||||
// TODO: Extract ClientScopeProvider from CacheRealmProvider and use that instead
|
||||
ClientScopeProvider cache = getProvider(CacheRealmProvider.class);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
} else {
|
||||
return clientScopeStorageManager();
|
||||
}
|
||||
}
|
||||
|
||||
private GroupProvider getGroupProvider() {
|
||||
// TODO: Extract GroupProvider from CacheRealmProvider and use that instead
|
||||
GroupProvider cache = getProvider(CacheRealmProvider.class);
|
||||
|
@ -204,6 +218,11 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
|||
return getProvider(ClientProvider.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeProvider clientScopeLocalStorage() {
|
||||
return getProvider(ClientScopeProvider.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupProvider groupLocalStorage() {
|
||||
return getProvider(GroupProvider.class);
|
||||
|
@ -217,6 +236,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
|||
return clientStorageManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeProvider clientScopeStorageManager() {
|
||||
if (clientScopeStorageManager == null) {
|
||||
clientScopeStorageManager = new ClientScopeStorageManager(this);
|
||||
}
|
||||
return clientScopeStorageManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleProvider roleLocalStorage() {
|
||||
return getProvider(RoleProvider.class);
|
||||
|
@ -350,6 +377,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
|||
return clientProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeProvider clientScopes() {
|
||||
if (clientScopeProvider == null) {
|
||||
clientScopeProvider = getClientScopeProvider();
|
||||
}
|
||||
return clientScopeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupProvider groups() {
|
||||
if (groupProvider == null) {
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2021 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.stream.Stream;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.storage.clientscope.ClientScopeLookupProvider;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProvider;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProviderFactory;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProviderModel;
|
||||
|
||||
public class ClientScopeStorageManager extends AbstractStorageManager<ClientScopeStorageProvider, ClientScopeStorageProviderModel> implements ClientScopeProvider {
|
||||
|
||||
public ClientScopeStorageManager(KeycloakSession session) {
|
||||
super(session, ClientScopeStorageProviderFactory.class, ClientScopeStorageProvider.class,
|
||||
ClientScopeStorageProviderModel::new, "clientscope");
|
||||
}
|
||||
|
||||
/* CLIENT SCOPE PROVIDER LOOKUP METHODS - implemented by client scope storage providers */
|
||||
|
||||
@Override
|
||||
public ClientScopeModel getClientScopeById(RealmModel realm, String id) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
if (storageId.getProviderId() == null) {
|
||||
return session.clientScopeLocalStorage().getClientScopeById(realm, id);
|
||||
}
|
||||
|
||||
ClientScopeLookupProvider provider = getStorageProviderInstance(realm, storageId.getProviderId(), ClientScopeLookupProvider.class);
|
||||
if (provider == null) return null;
|
||||
|
||||
return provider.getClientScopeById(realm, id);
|
||||
}
|
||||
|
||||
/* CLIENT SCOPE PROVIDER METHODS - provided only by local storage (e.g. not supported by storage providers) */
|
||||
|
||||
@Override
|
||||
public Stream<ClientScopeModel> getClientScopesStream(RealmModel realm) {
|
||||
return session.clientScopeLocalStorage().getClientScopesStream(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel addClientScope(RealmModel realm, String id, String name) {
|
||||
return session.clientScopeLocalStorage().addClientScope(realm, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeClientScope(RealmModel realm, String id) {
|
||||
return session.clientScopeLocalStorage().removeClientScope(realm, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientScopes(RealmModel realm) {
|
||||
session.clientScopeLocalStorage().removeClientScopes(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright 2021 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.testsuite.federation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.clientscope.ClientScopeLookupProvider;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProvider;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProviderModel;
|
||||
|
||||
|
||||
public class HardcodedClientScopeStorageProvider implements ClientScopeStorageProvider, ClientScopeLookupProvider {
|
||||
|
||||
private final ClientScopeStorageProviderModel component;
|
||||
private final String clientScopeName;
|
||||
|
||||
public HardcodedClientScopeStorageProvider(KeycloakSession session, ClientScopeStorageProviderModel component) {
|
||||
this.component = component;
|
||||
this.clientScopeName = component.getConfig().getFirst(HardcodedClientScopeStorageProviderFactory.SCOPE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientScopeModel getClientScopeById(RealmModel realm, String id) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
final String scopeName = storageId.getExternalId();
|
||||
if (this.clientScopeName.equals(scopeName)) return new HardcodedClientScopeAdapter(realm);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
public class HardcodedClientScopeAdapter implements ClientScopeModel {
|
||||
|
||||
private final RealmModel realm;
|
||||
private StorageId storageId;
|
||||
|
||||
public HardcodedClientScopeAdapter(RealmModel realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
if (storageId == null) {
|
||||
storageId = new StorageId(component.getId(), getName());
|
||||
}
|
||||
return storageId.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return clientScopeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Federated client scope";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol() {
|
||||
return "openid-connect";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocol(String protocol) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttribute(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ProtocolMapperModel> getProtocolMappersStream() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProtocolMapper(ProtocolMapperModel mapping) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProtocolMapper(ProtocolMapperModel mapping) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperById(String id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getScopeMappingsStream() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRealmScopeMappingsStream() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addScopeMapping(RoleModel role) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteScopeMapping(RoleModel role) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScope(RoleModel role) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2021 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.testsuite.federation;
|
||||
|
||||
import java.util.List;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProviderFactory;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProviderModel;
|
||||
|
||||
public class HardcodedClientScopeStorageProviderFactory implements ClientScopeStorageProviderFactory<HardcodedClientScopeStorageProvider> {
|
||||
|
||||
public static final String PROVIDER_ID = "hardcoded-clientscope";
|
||||
public static final String SCOPE_NAME = "scope_name";
|
||||
protected static final List<ProviderConfigProperty> CONFIG_PROPERTIES;
|
||||
|
||||
@Override
|
||||
public HardcodedClientScopeStorageProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new HardcodedClientScopeStorageProvider(session, new ClientScopeStorageProviderModel(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
static {
|
||||
CONFIG_PROPERTIES = ProviderConfigurationBuilder.create()
|
||||
.property().name(SCOPE_NAME)
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.label("Hardcoded Scope Name")
|
||||
.helpText("Only this scope name is available for lookup")
|
||||
.defaultValue("hardcoded-clientscope")
|
||||
.add()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return CONFIG_PROPERTIES;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# Copyright 2021 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.
|
||||
#
|
||||
org.keycloak.testsuite.federation.HardcodedClientScopeStorageProviderFactory
|
|
@ -51,6 +51,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
@ -82,7 +83,8 @@ public class ClientScopeTest extends AbstractClientTest {
|
|||
|
||||
@Test (expected = NotFoundException.class)
|
||||
public void testGetUnknownScope() {
|
||||
clientScopes().get("unknown-id").toRepresentation();
|
||||
String unknownId = UUID.randomUUID().toString();
|
||||
clientScopes().get(unknownId).toRepresentation();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -257,7 +257,7 @@ public class ExportImportTest extends AbstractKeycloakTest {
|
|||
File testRealm = new File(url.getFile());
|
||||
assertThat(testRealm, Matchers.notNullValue());
|
||||
|
||||
File newFile = new File("test-new-realm.json");
|
||||
File newFile = new File("target", "test-new-realm.json");
|
||||
|
||||
try {
|
||||
FileUtils.copyFile(testRealm, newFile);
|
||||
|
|
|
@ -48,6 +48,10 @@
|
|||
"provider": "${keycloak.client.provider:jpa}"
|
||||
},
|
||||
|
||||
"clientScope": {
|
||||
"provider": "${keycloak.clientScope.provider:jpa}"
|
||||
},
|
||||
|
||||
"group": {
|
||||
"provider": "${keycloak.group.provider:jpa}"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2021 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.testsuite.model;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RealmProvider;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProvider;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProviderModel;
|
||||
import org.keycloak.testsuite.federation.HardcodedClientScopeStorageProviderFactory;
|
||||
|
||||
@RequireProvider(RealmProvider.class)
|
||||
@RequireProvider(ClientScopeStorageProvider.class)
|
||||
public class ClientScopeStorageTest extends KeycloakModelTest {
|
||||
|
||||
private String realmId;
|
||||
private String clientScopeFederationId;
|
||||
|
||||
@Override
|
||||
public void createEnvironment(KeycloakSession s) {
|
||||
RealmModel realm = s.realms().createRealm("realm");
|
||||
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||
this.realmId = realm.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanEnvironment(KeycloakSession s) {
|
||||
s.realms().removeRealm(realmId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetClientScopeById() {
|
||||
getParameters(ClientScopeStorageProviderModel.class).forEach(fs -> inComittedTransaction(fs, (session, federatedStorage) -> {
|
||||
Assume.assumeThat("Cannot handle more than 1 client scope federation provider", clientScopeFederationId, Matchers.nullValue());
|
||||
RealmModel realm = session.realms().getRealm(realmId);
|
||||
federatedStorage.setParentId(realmId);
|
||||
federatedStorage.setEnabled(true);
|
||||
federatedStorage.getConfig().putSingle(HardcodedClientScopeStorageProviderFactory.SCOPE_NAME, HardcodedClientScopeStorageProviderFactory.SCOPE_NAME);
|
||||
ComponentModel res = realm.addComponentModel(federatedStorage);
|
||||
clientScopeFederationId = res.getId();
|
||||
log.infof("Added %s client scope federation provider: %s", federatedStorage.getName(), clientScopeFederationId);
|
||||
}));
|
||||
|
||||
inComittedTransaction(1, (session, i) -> {
|
||||
final RealmModel realm = session.realms().getRealm(realmId);
|
||||
StorageId storageId = new StorageId(clientScopeFederationId, "scope_name");
|
||||
ClientScopeModel hardcoded = session.clientScopes().getClientScopeById(realm, storageId.getId());
|
||||
Assert.assertNotNull(hardcoded);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import org.keycloak.events.EventStoreSpi;
|
|||
import org.keycloak.executors.DefaultExecutorsProviderFactory;
|
||||
import org.keycloak.executors.ExecutorsSpi;
|
||||
import org.keycloak.models.AbstractKeycloakTransaction;
|
||||
import org.keycloak.models.ClientScopeSpi;
|
||||
import org.keycloak.models.ClientSpi;
|
||||
import org.keycloak.models.GroupSpi;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -141,6 +142,7 @@ public abstract class KeycloakModelTest {
|
|||
|
||||
private static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
|
||||
.add(AuthorizationSpi.class)
|
||||
.add(ClientScopeSpi.class)
|
||||
.add(ClientSpi.class)
|
||||
.add(ClusterSpi.class)
|
||||
.add(EventStoreSpi.class)
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.events.jpa.JpaEventStoreProviderFactory;
|
|||
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
||||
import org.keycloak.models.dblock.DBLockSpi;
|
||||
import org.keycloak.models.jpa.JpaClientProviderFactory;
|
||||
import org.keycloak.models.jpa.JpaClientScopeProviderFactory;
|
||||
import org.keycloak.models.jpa.JpaGroupProviderFactory;
|
||||
import org.keycloak.models.jpa.JpaRealmProviderFactory;
|
||||
import org.keycloak.models.jpa.JpaRoleProviderFactory;
|
||||
|
@ -57,6 +58,7 @@ public class Jpa extends KeycloakModelParameters {
|
|||
.add(DefaultJpaConnectionProviderFactory.class)
|
||||
.add(JPAAuthorizationStoreFactory.class)
|
||||
.add(JpaClientProviderFactory.class)
|
||||
.add(JpaClientScopeProviderFactory.class)
|
||||
.add(JpaEventStoreProviderFactory.class)
|
||||
.add(JpaGroupProviderFactory.class)
|
||||
.add(JpaRealmProviderFactory.class)
|
||||
|
|
|
@ -22,9 +22,15 @@ import org.keycloak.provider.Spi;
|
|||
import org.keycloak.storage.UserStorageProviderSpi;
|
||||
import org.keycloak.storage.federated.UserFederatedStorageProviderSpi;
|
||||
import org.keycloak.storage.jpa.JpaUserFederatedStorageProviderFactory;
|
||||
import org.keycloak.testsuite.federation.BackwardsCompatibilityUserStorageFactory;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProvider;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProviderFactory;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProviderModel;
|
||||
import org.keycloak.storage.clientscope.ClientScopeStorageProviderSpi;
|
||||
import org.keycloak.testsuite.federation.HardcodedClientScopeStorageProviderFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -32,19 +38,36 @@ import java.util.Set;
|
|||
*/
|
||||
public class JpaFederation extends KeycloakModelParameters {
|
||||
|
||||
private final AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
|
||||
.addAll(Jpa.ALLOWED_SPIS)
|
||||
.add(UserStorageProviderSpi.class)
|
||||
.add(UserFederatedStorageProviderSpi.class)
|
||||
.add(ClientScopeStorageProviderSpi.class)
|
||||
|
||||
.build();
|
||||
|
||||
static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
|
||||
.addAll(Jpa.ALLOWED_FACTORIES)
|
||||
.add(JpaUserFederatedStorageProviderFactory.class)
|
||||
.add(ClientScopeStorageProviderFactory.class)
|
||||
.build();
|
||||
|
||||
public JpaFederation() {
|
||||
super(ALLOWED_SPIS, ALLOWED_FACTORIES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Stream<T> getParameters(Class<T> clazz) {
|
||||
if (ClientScopeStorageProviderModel.class.isAssignableFrom(clazz)) {
|
||||
ClientScopeStorageProviderModel federatedStorage = new ClientScopeStorageProviderModel();
|
||||
federatedStorage.setName(HardcodedClientScopeStorageProviderFactory.PROVIDER_ID + ":" + counter.getAndIncrement());
|
||||
federatedStorage.setProviderId(HardcodedClientScopeStorageProviderFactory.PROVIDER_ID);
|
||||
federatedStorage.setProviderType(ClientScopeStorageProvider.class.getName());
|
||||
return Stream.of((T) federatedStorage);
|
||||
} else {
|
||||
return super.getParameters(clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
"provider": "${keycloak.client.provider:jpa}"
|
||||
},
|
||||
|
||||
"clientScope": {
|
||||
"provider": "${keycloak.clientScope.provider:jpa}"
|
||||
},
|
||||
|
||||
"group": {
|
||||
"provider": "${keycloak.group.provider:jpa}"
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue