mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-05 06:30:09 -05:00
Map Store Removal: Rename legacy modules
Closes #24107 Signed-off-by: Martin Kanis <mkanis@redhat.com>
This commit is contained in:
committed by
Alexander Schwartz
parent
28c9f98930
commit
7797f778d1
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-model-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-model-storage</artifactId>
|
||||
<name>Keycloak Database Support</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi-private</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright 2022. 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.credential;
|
||||
|
||||
import org.keycloak.common.util.reflections.Types;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.SubjectCredentialManager;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.AbstractStorageManager;
|
||||
import org.keycloak.storage.DatastoreProvider;
|
||||
import org.keycloak.storage.StoreManagers;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderFactory;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Handling credentials for a given user for the store.
|
||||
*
|
||||
* @author Alexander Schwartz
|
||||
*/
|
||||
public class UserCredentialManager extends AbstractStorageManager<UserStorageProvider, UserStorageProviderModel> implements SubjectCredentialManager {
|
||||
|
||||
private final UserModel user;
|
||||
private final KeycloakSession session;
|
||||
private final RealmModel realm;
|
||||
|
||||
public UserCredentialManager(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
super(session, UserStorageProviderFactory.class, UserStorageProvider.class, UserStorageProviderModel::new, "user");
|
||||
this.user = user;
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(List<CredentialInput> inputs) {
|
||||
if (!isValid(user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<CredentialInput> toValidate = new LinkedList<>(inputs);
|
||||
|
||||
String providerId = StorageId.isLocalStorage(user.getId()) ? user.getFederationLink() : StorageId.providerId(user.getId());
|
||||
if (providerId != null) {
|
||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||
if (model == null || !model.isEnabled()) return false;
|
||||
|
||||
CredentialInputValidator validator = getStorageProviderInstance(model, CredentialInputValidator.class);
|
||||
if (validator != null) {
|
||||
validate(realm, user, toValidate, validator);
|
||||
}
|
||||
}
|
||||
|
||||
getCredentialProviders(session, CredentialInputValidator.class)
|
||||
.forEach(validator -> validate(realm, user, toValidate, validator));
|
||||
|
||||
return toValidate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(CredentialInput input) {
|
||||
String providerId = StorageId.isLocalStorage(user.getId()) ? user.getFederationLink() : StorageId.providerId(user.getId());
|
||||
if (!StorageId.isLocalStorage(user.getId())) throwExceptionIfInvalidUser(user);
|
||||
|
||||
if (providerId != null) {
|
||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||
if (model == null || !model.isEnabled()) return false;
|
||||
|
||||
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
|
||||
if (updater != null && updater.supportsCredentialType(input.getType())) {
|
||||
if (updater.updateCredential(realm, user, input)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return getCredentialProviders(session, CredentialInputUpdater.class)
|
||||
.filter(updater -> updater.supportsCredentialType(input.getType()))
|
||||
.anyMatch(updater -> updater.updateCredential(realm, user, input));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStoredCredential(CredentialModel cred) {
|
||||
throwExceptionIfInvalidUser(user);
|
||||
getStoreForUser(user).updateCredential(realm, user, cred);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialModel createStoredCredential(CredentialModel cred) {
|
||||
throwExceptionIfInvalidUser(user);
|
||||
return getStoreForUser(user).createCredential(realm, user, cred);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeStoredCredentialById(String id) {
|
||||
throwExceptionIfInvalidUser(user);
|
||||
return getStoreForUser(user).removeStoredCredential(realm, user, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialModel getStoredCredentialById(String id) {
|
||||
return getStoreForUser(user).getStoredCredentialById(realm, user, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<CredentialModel> getStoredCredentialsStream() {
|
||||
return getStoreForUser(user).getStoredCredentialsStream(realm, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
|
||||
return getStoreForUser(user).getStoredCredentialsByTypeStream(realm, user, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
|
||||
return getStoreForUser(user).getStoredCredentialByNameAndType(realm, user, name, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveStoredCredentialTo(String id, String newPreviousCredentialId) {
|
||||
throwExceptionIfInvalidUser(user);
|
||||
return getStoreForUser(user).moveCredentialTo(realm, user, id, newPreviousCredentialId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCredentialLabel(String credentialId, String userLabel) {
|
||||
throwExceptionIfInvalidUser(user);
|
||||
CredentialModel credential = getStoredCredentialById(credentialId);
|
||||
credential.setUserLabel(userLabel);
|
||||
updateStoredCredential(credential);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCredentialType(String credentialType) {
|
||||
String providerId = StorageId.isLocalStorage(user.getId()) ? user.getFederationLink() : StorageId.providerId(user.getId());
|
||||
if (!StorageId.isLocalStorage(user.getId())) throwExceptionIfInvalidUser(user);
|
||||
if (providerId != null) {
|
||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||
if (model == null || !model.isEnabled()) return;
|
||||
|
||||
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
|
||||
if (updater.supportsCredentialType(credentialType)) {
|
||||
updater.disableCredentialType(realm, user, credentialType);
|
||||
}
|
||||
}
|
||||
|
||||
getCredentialProviders(session, CredentialInputUpdater.class)
|
||||
.filter(updater -> updater.supportsCredentialType(credentialType))
|
||||
.forEach(updater -> updater.disableCredentialType(realm, user, credentialType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getDisableableCredentialTypesStream() {
|
||||
Stream<String> types = Stream.empty();
|
||||
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
|
||||
if (providerId != null) {
|
||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||
if (model == null || !model.isEnabled()) return types;
|
||||
|
||||
CredentialInputUpdater updater = getStorageProviderInstance(model, CredentialInputUpdater.class);
|
||||
if (updater != null) types = updater.getDisableableCredentialTypesStream(realm, user);
|
||||
}
|
||||
|
||||
return Stream.concat(types, getCredentialProviders(session, CredentialInputUpdater.class)
|
||||
.flatMap(updater -> updater.getDisableableCredentialTypesStream(realm, user)))
|
||||
.distinct();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfiguredFor(String type) {
|
||||
UserStorageCredentialConfigured userStorageConfigured = isConfiguredThroughUserStorage(realm, user, type);
|
||||
|
||||
// Check if we can rely just on userStorage to decide if credential is configured for the user or not
|
||||
switch (userStorageConfigured) {
|
||||
case CONFIGURED: return true;
|
||||
case USER_STORAGE_DISABLED: return false;
|
||||
}
|
||||
|
||||
// Check locally as a fallback
|
||||
return isConfiguredLocally(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfiguredLocally(String type) {
|
||||
return getCredentialProviders(session, CredentialInputValidator.class)
|
||||
.anyMatch(validator -> validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getConfiguredUserStorageCredentialTypesStream() {
|
||||
return getCredentialProviders(session, CredentialProvider.class).map(CredentialProvider::getType)
|
||||
.filter(credentialType -> UserStorageCredentialConfigured.CONFIGURED == isConfiguredThroughUserStorage(realm, user, credentialType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialModel createCredentialThroughProvider(CredentialModel model) {
|
||||
throwExceptionIfInvalidUser(user);
|
||||
return session.getKeycloakSessionFactory()
|
||||
.getProviderFactoriesStream(CredentialProvider.class)
|
||||
.map(f -> session.getProvider(CredentialProvider.class, f.getId()))
|
||||
.filter(provider -> Objects.equals(provider.getType(), model.getType()))
|
||||
.map(cp -> cp.createCredential(realm, user, cp.getCredentialFromModel(model)))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private enum UserStorageCredentialConfigured {
|
||||
CONFIGURED,
|
||||
USER_STORAGE_DISABLED,
|
||||
NOT_CONFIGURED
|
||||
}
|
||||
|
||||
private UserStorageCredentialConfigured isConfiguredThroughUserStorage(RealmModel realm, UserModel user, String type) {
|
||||
String providerId = StorageId.isLocalStorage(user) ? user.getFederationLink() : StorageId.resolveProviderId(user);
|
||||
if (providerId != null) {
|
||||
UserStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||
if (model == null || !model.isEnabled()) return UserStorageCredentialConfigured.USER_STORAGE_DISABLED;
|
||||
|
||||
CredentialInputValidator validator = getStorageProviderInstance(model, CredentialInputValidator.class);
|
||||
if (validator != null && validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
|
||||
return UserStorageCredentialConfigured.CONFIGURED;
|
||||
}
|
||||
}
|
||||
|
||||
return UserStorageCredentialConfigured.NOT_CONFIGURED;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean isValid(UserModel user) {
|
||||
Objects.requireNonNull(user);
|
||||
return user.getServiceAccountClientLink() == null;
|
||||
}
|
||||
|
||||
private void validate(RealmModel realm, UserModel user, List<CredentialInput> toValidate, CredentialInputValidator validator) {
|
||||
toValidate.removeIf(input -> validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input));
|
||||
}
|
||||
|
||||
private static <T> Stream<T> getCredentialProviders(KeycloakSession session, Class<T> type) {
|
||||
//noinspection unchecked
|
||||
return session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class)
|
||||
.filter(f -> Types.supports(type, f, CredentialProviderFactory.class))
|
||||
.map(f -> (T) session.getProvider(CredentialProvider.class, f.getId()));
|
||||
}
|
||||
|
||||
private void throwExceptionIfInvalidUser(UserModel user) {
|
||||
if (!isValid(user)) {
|
||||
throw new RuntimeException("You can not manage credentials for this user");
|
||||
}
|
||||
}
|
||||
|
||||
private UserCredentialStore getStoreForUser(UserModel user) {
|
||||
StoreManagers p = (StoreManagers) session.getProvider(DatastoreProvider.class);
|
||||
if (StorageId.isLocalStorage(user.getId())) {
|
||||
return (UserCredentialStore) p.userLocalStorage();
|
||||
} else {
|
||||
return (UserCredentialStore) p.userFederatedStorage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2022 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.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
import org.keycloak.storage.client.ClientStorageProvider;
|
||||
import org.keycloak.storage.client.ClientStorageProviderModel;
|
||||
import org.keycloak.storage.role.RoleStorageProvider;
|
||||
import org.keycloak.storage.role.RoleStorageProviderModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Alexander Schwartz
|
||||
*/
|
||||
public interface StorageProviderRealmModel extends RealmModel {
|
||||
/**
|
||||
* @deprecated Use {@link #getClientStorageProvidersStream() getClientStorageProvidersStream} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default List<ClientStorageProviderModel> getClientStorageProviders() {
|
||||
return getClientStorageProvidersStream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sorted {@link ClientStorageProviderModel ClientStorageProviderModel} as a stream.
|
||||
* It should be used with forEachOrdered if the ordering is required.
|
||||
* @return Sorted stream of {@link ClientStorageProviderModel}. Never returns {@code null}.
|
||||
*/
|
||||
default Stream<ClientStorageProviderModel> getClientStorageProvidersStream() {
|
||||
return getComponentsStream(getId(), ClientStorageProvider.class.getName())
|
||||
.map(ClientStorageProviderModel::new)
|
||||
.sorted(ClientStorageProviderModel.comparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getRoleStorageProvidersStream() getRoleStorageProvidersStream} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default List<RoleStorageProviderModel> getRoleStorageProviders() {
|
||||
return getRoleStorageProvidersStream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sorted {@link RoleStorageProviderModel RoleStorageProviderModel} as a stream.
|
||||
* It should be used with forEachOrdered if the ordering is required.
|
||||
* @return Sorted stream of {@link RoleStorageProviderModel}. Never returns {@code null}.
|
||||
*/
|
||||
default Stream<RoleStorageProviderModel> getRoleStorageProvidersStream() {
|
||||
return getComponentsStream(getId(), RoleStorageProvider.class.getName())
|
||||
.map(RoleStorageProviderModel::new)
|
||||
.sorted(RoleStorageProviderModel.comparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getUserStorageProvidersStream() getUserStorageProvidersStream} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default List<UserStorageProviderModel> getUserStorageProviders() {
|
||||
return getUserStorageProvidersStream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sorted {@link UserStorageProviderModel UserStorageProviderModel} as a stream.
|
||||
* It should be used with forEachOrdered if the ordering is required.
|
||||
* @return Sorted stream of {@link UserStorageProviderModel}. Never returns {@code null}.
|
||||
*/
|
||||
default Stream<UserStorageProviderModel> getUserStorageProvidersStream() {
|
||||
return getComponentsStream(getId(), UserStorageProvider.class.getName())
|
||||
.map(UserStorageProviderModel::new)
|
||||
.sorted(UserStorageProviderModel.comparator);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.models.cache;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface CachedObject {
|
||||
long getCacheTimestamp();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2022 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;
|
||||
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Cached users will implement this interface
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface CachedUserModel extends UserModel {
|
||||
|
||||
/**
|
||||
* Invalidates the cache for this user and returns a delegate that represents the actual data provider
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
UserModel getDelegateForUpdate();
|
||||
|
||||
boolean isMarkedForEviction();
|
||||
|
||||
/**
|
||||
* Invalidate the cache for this model
|
||||
*
|
||||
*/
|
||||
void invalidate();
|
||||
|
||||
/**
|
||||
* When was the model was loaded from database.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
long getCacheTimestamp();
|
||||
|
||||
/**
|
||||
* Returns a map that contains custom things that are cached along with this model. You can write to this map.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
ConcurrentMap getCachedWith();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2022 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;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface OnUserCache {
|
||||
void onCache(RealmModel realm, CachedUserModel user, UserModel delegate);
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.cache;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
|
||||
/**
|
||||
* All these methods effect an entire cluster of Keycloak instances.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserCache extends UserProvider {
|
||||
/**
|
||||
* Evict user from cache.
|
||||
*
|
||||
* @param user
|
||||
*/
|
||||
void evict(RealmModel realm, UserModel user);
|
||||
|
||||
/**
|
||||
* Evict users of a specific realm
|
||||
*
|
||||
* @param realm
|
||||
*/
|
||||
void evict(RealmModel realm);
|
||||
|
||||
/**
|
||||
* Clear cache entirely.
|
||||
*
|
||||
*/
|
||||
void clear();
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2020 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 org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.util.reflections.Types;
|
||||
import org.keycloak.component.ComponentFactory;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.utils.ServicesUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param <ProviderType> This type will be used for looking for factories that produce instances of desired providers
|
||||
* @param <StorageProviderModelType> Type of model used for creating provider, it needs to extend
|
||||
* CacheableStorageProviderModel as it has {@code isEnabled()} method and also extend
|
||||
* PrioritizedComponentModel which is required for sorting providers based on its
|
||||
* priorities
|
||||
*/
|
||||
public abstract class AbstractStorageManager<ProviderType extends Provider,
|
||||
StorageProviderModelType extends CacheableStorageProviderModel> {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(AbstractStorageManager.class);
|
||||
|
||||
/**
|
||||
* Timeouts are used as time boundary for obtaining models from an external storage. Default value is set
|
||||
* to 3000 milliseconds and it's configurable.
|
||||
*/
|
||||
private static final Long STORAGE_PROVIDER_DEFAULT_TIMEOUT = 3000L;
|
||||
protected final KeycloakSession session;
|
||||
private final Class<ProviderType> providerTypeClass;
|
||||
private final Class<? extends ProviderFactory> factoryTypeClass;
|
||||
private final Function<ComponentModel, StorageProviderModelType> toStorageProviderModelTypeFunction;
|
||||
private final String configScope;
|
||||
private Long storageProviderTimeout;
|
||||
|
||||
public AbstractStorageManager(KeycloakSession session, Class<? extends ProviderFactory> factoryTypeClass, Class<ProviderType> providerTypeClass, Function<ComponentModel, StorageProviderModelType> toStorageProviderModelTypeFunction, String configScope) {
|
||||
this.session = session;
|
||||
this.providerTypeClass = providerTypeClass;
|
||||
this.factoryTypeClass = factoryTypeClass;
|
||||
this.toStorageProviderModelTypeFunction = toStorageProviderModelTypeFunction;
|
||||
this.configScope = configScope;
|
||||
}
|
||||
|
||||
protected Long getStorageProviderTimeout() {
|
||||
if (storageProviderTimeout == null) {
|
||||
storageProviderTimeout = Config.scope(configScope).getLong("storageProviderTimeout", STORAGE_PROVIDER_DEFAULT_TIMEOUT);
|
||||
}
|
||||
return storageProviderTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a factory with the providerId, which produce instances of type CreatedProviderType
|
||||
* @param providerId id of factory that produce desired instances
|
||||
* @return A factory that implements {@code ComponentFactory<CreatedProviderType, ProviderType>}
|
||||
*/
|
||||
protected <T extends ProviderType> ComponentFactory<T, ProviderType> getStorageProviderFactory(String providerId) {
|
||||
return (ComponentFactory<T, ProviderType>) session.getKeycloakSessionFactory()
|
||||
.getProviderFactory(providerTypeClass, providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns stream of all storageProviders within the realm that implements the capabilityInterface.
|
||||
*
|
||||
* @param realm realm
|
||||
* @param capabilityInterface class of desired capabilityInterface.
|
||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
||||
* @return enabled storage providers for realm and @{code getProviderTypeClass()}
|
||||
*/
|
||||
protected <T> Stream<T> getEnabledStorageProviders(RealmModel realm, Class<T> capabilityInterface) {
|
||||
return getStorageProviderModels(realm, providerTypeClass)
|
||||
.map(toStorageProviderModelTypeFunction)
|
||||
.filter(StorageProviderModelType::isEnabled)
|
||||
.sorted(StorageProviderModelType.comparator)
|
||||
.map(storageProviderModelType -> getStorageProviderInstance(storageProviderModelType, capabilityInterface, false))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all enabled StorageProviders that implements the capabilityInterface, applies applyFunction on each of
|
||||
* them and then join the results together.
|
||||
*
|
||||
* !! Each StorageProvider has a limited time to respond, if it fails to do it, empty stream is returned !!
|
||||
*
|
||||
* @param realm realm
|
||||
* @param capabilityInterface class of desired capabilityInterface.
|
||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
||||
* @param applyFunction function that is applied on StorageProviders
|
||||
* @param <R> result of applyFunction
|
||||
* @return a stream with all results from all StorageProviders
|
||||
*/
|
||||
protected <R, T> Stream<R> flatMapEnabledStorageProvidersWithTimeout(RealmModel realm, Class<T> capabilityInterface, Function<T, ? extends Stream<R>> applyFunction) {
|
||||
return getEnabledStorageProviders(realm, capabilityInterface)
|
||||
.flatMap(ServicesUtils.timeBound(session, getStorageProviderTimeout(), applyFunction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all enabled StorageProviders that implements the capabilityInterface, applies applyFunction on each of
|
||||
* them and returns the stream.
|
||||
*
|
||||
* !! Each StorageProvider has a limited time to respond, if it fails to do it, null is returned !!
|
||||
*
|
||||
* @param realm realm
|
||||
* @param capabilityInterface class of desired capabilityInterface.
|
||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
||||
* @param applyFunction function that is applied on StorageProviders
|
||||
* @param <R> Result of applyFunction
|
||||
* @return First result from StorageProviders
|
||||
*/
|
||||
protected <R, T> Stream<R> mapEnabledStorageProvidersWithTimeout(RealmModel realm, Class<T> capabilityInterface, Function<T, R> applyFunction) {
|
||||
return getEnabledStorageProviders(realm, capabilityInterface)
|
||||
.map(ServicesUtils.timeBoundOne(session, getStorageProviderTimeout(), applyFunction))
|
||||
.filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all enabled StorageProviders that implements the capabilityInterface and call applyFunction on each
|
||||
*
|
||||
* !! Each StorageProvider has a limited time for consuming !!
|
||||
*
|
||||
* @param realm realm
|
||||
* @param capabilityInterface class of desired capabilityInterface.
|
||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
||||
* @param consumer function that is applied on StorageProviders
|
||||
*/
|
||||
protected <T> void consumeEnabledStorageProvidersWithTimeout(RealmModel realm, Class<T> capabilityInterface, Consumer<T> consumer) {
|
||||
getEnabledStorageProviders(realm, capabilityInterface)
|
||||
.forEachOrdered(ServicesUtils.consumeWithTimeBound(session, getStorageProviderTimeout(), consumer));
|
||||
}
|
||||
|
||||
|
||||
protected <T> T getStorageProviderInstance(RealmModel realm, String providerId, Class<T> capabilityInterface) {
|
||||
return getStorageProviderInstance(realm, providerId, capabilityInterface, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of provider with the providerId within the realm or null if storage provider with providerId
|
||||
* doesn't implement capabilityInterface.
|
||||
*
|
||||
* @param realm realm
|
||||
* @param providerId id of ComponentModel within database/storage
|
||||
* @param capabilityInterface class of desired capabilityInterface.
|
||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
||||
* @return an instance of type CreatedProviderType or null if storage provider with providerId doesn't implement capabilityInterface
|
||||
*/
|
||||
protected <T> T getStorageProviderInstance(RealmModel realm, String providerId, Class<T> capabilityInterface, boolean includeDisabled) {
|
||||
if (providerId == null || capabilityInterface == null) return null;
|
||||
return getStorageProviderInstance(getStorageProviderModel(realm, providerId), capabilityInterface, includeDisabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of StorageProvider model corresponding realm and providerId
|
||||
* @param realm Realm.
|
||||
* @param providerId Id of desired provider.
|
||||
* @return An instance of type StorageProviderModelType
|
||||
*/
|
||||
protected StorageProviderModelType getStorageProviderModel(RealmModel realm, String providerId) {
|
||||
ComponentModel componentModel = realm.getComponent(providerId);
|
||||
if (componentModel == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return toStorageProviderModelTypeFunction.apply(componentModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of provider for the model or null if storage provider based on the model doesn't implement capabilityInterface.
|
||||
*
|
||||
* @param model StorageProviderModel obtained from database/storage
|
||||
* @param capabilityInterface class of desired capabilityInterface.
|
||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
||||
* @param <T> Required capability interface type
|
||||
* @return an instance of type T or null if storage provider based on the model doesn't exist or doesn't implement the capabilityInterface.
|
||||
*/
|
||||
protected <T> T getStorageProviderInstance(StorageProviderModelType model, Class<T> capabilityInterface) {
|
||||
return getStorageProviderInstance(model, capabilityInterface, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of provider for the model or null if storage provider based on the model doesn't implement capabilityInterface.
|
||||
*
|
||||
* @param model StorageProviderModel obtained from database/storage
|
||||
* @param capabilityInterface class of desired capabilityInterface.
|
||||
* For example, {@code GroupLookupProvider} or {@code UserQueryProvider}
|
||||
* @param includeDisabled If set to true, the method will return also disabled providers.
|
||||
* @return an instance of type T or null if storage provider based on the model doesn't exist or doesn't implement the capabilityInterface.
|
||||
*/
|
||||
protected <T> T getStorageProviderInstance(StorageProviderModelType model, Class<T> capabilityInterface, boolean includeDisabled) {
|
||||
if (model == null || (!model.isEnabled() && !includeDisabled) || capabilityInterface == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ProviderType instance = (ProviderType) session.getAttribute(model.getId());
|
||||
if (instance != null && capabilityInterface.isAssignableFrom(instance.getClass())) return capabilityInterface.cast(instance);
|
||||
|
||||
ComponentFactory<? extends ProviderType, ProviderType> factory = getStorageProviderFactory(model.getProviderId());
|
||||
if (factory == null) {
|
||||
LOG.warnv("Configured StorageProvider {0} of provider id {1} does not exist", model.getName(), model.getProviderId());
|
||||
return null;
|
||||
}
|
||||
if (!Types.supports(capabilityInterface, factory, factoryTypeClass)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
instance = factory.create(session, model);
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException("StorageProviderFactory (of type " + factory.getClass().getName() + ") produced a null instance");
|
||||
}
|
||||
session.enlistForClose(instance);
|
||||
session.setAttribute(model.getId(), instance);
|
||||
return capabilityInterface.cast(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream of ComponentModels of storageType.
|
||||
* @param realm Realm.
|
||||
* @param storageType Type.
|
||||
* @return Stream of ComponentModels
|
||||
*/
|
||||
public static Stream<ComponentModel> getStorageProviderModels(RealmModel realm, Class<? extends Provider> storageType) {
|
||||
return realm.getStorageProviders(storageType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.PrioritizedComponentModel;
|
||||
import org.keycloak.models.cache.CachedObject;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CacheableStorageProviderModel extends PrioritizedComponentModel {
|
||||
public static final String CACHE_POLICY = "cachePolicy";
|
||||
public static final String MAX_LIFESPAN = "maxLifespan";
|
||||
public static final String EVICTION_HOUR = "evictionHour";
|
||||
public static final String EVICTION_MINUTE = "evictionMinute";
|
||||
public static final String EVICTION_DAY = "evictionDay";
|
||||
public static final String CACHE_INVALID_BEFORE = "cacheInvalidBefore";
|
||||
public static final String ENABLED = "enabled";
|
||||
|
||||
private transient CachePolicy cachePolicy;
|
||||
private transient long maxLifespan = -1;
|
||||
private transient int evictionHour = -1;
|
||||
private transient int evictionMinute = -1;
|
||||
private transient int evictionDay = -1;
|
||||
private transient long cacheInvalidBefore = -1;
|
||||
private transient Boolean enabled;
|
||||
|
||||
public CacheableStorageProviderModel() {
|
||||
}
|
||||
|
||||
public CacheableStorageProviderModel(ComponentModel copy) {
|
||||
super(copy);
|
||||
}
|
||||
|
||||
public CachePolicy getCachePolicy() {
|
||||
if (cachePolicy == null) {
|
||||
String str = getConfig().getFirst(CACHE_POLICY);
|
||||
if (str == null) return null;
|
||||
cachePolicy = CachePolicy.valueOf(str);
|
||||
}
|
||||
return cachePolicy;
|
||||
}
|
||||
|
||||
public void setCachePolicy(CachePolicy cachePolicy) {
|
||||
this.cachePolicy = cachePolicy;
|
||||
if (cachePolicy == null) {
|
||||
getConfig().remove(CACHE_POLICY);
|
||||
|
||||
} else {
|
||||
getConfig().putSingle(CACHE_POLICY, cachePolicy.name());
|
||||
}
|
||||
}
|
||||
|
||||
public long getMaxLifespan() {
|
||||
if (maxLifespan < 0) {
|
||||
String str = getConfig().getFirst(MAX_LIFESPAN);
|
||||
if (str == null) return -1;
|
||||
maxLifespan = Long.valueOf(str);
|
||||
}
|
||||
return maxLifespan;
|
||||
}
|
||||
|
||||
public void setMaxLifespan(long maxLifespan) {
|
||||
this.maxLifespan = maxLifespan;
|
||||
getConfig().putSingle(MAX_LIFESPAN, Long.toString(maxLifespan));
|
||||
}
|
||||
|
||||
public int getEvictionHour() {
|
||||
if (evictionHour < 0) {
|
||||
String str = getConfig().getFirst(EVICTION_HOUR);
|
||||
if (str == null) return -1;
|
||||
evictionHour = Integer.valueOf(str);
|
||||
}
|
||||
return evictionHour;
|
||||
}
|
||||
|
||||
public void setEvictionHour(int evictionHour) {
|
||||
if (evictionHour > 23 || evictionHour < 0) throw new IllegalArgumentException("Must be between 0 and 23");
|
||||
this.evictionHour = evictionHour;
|
||||
getConfig().putSingle(EVICTION_HOUR, Integer.toString(evictionHour));
|
||||
}
|
||||
|
||||
public int getEvictionMinute() {
|
||||
if (evictionMinute < 0) {
|
||||
String str = getConfig().getFirst(EVICTION_MINUTE);
|
||||
if (str == null) return -1;
|
||||
evictionMinute = Integer.valueOf(str);
|
||||
}
|
||||
return evictionMinute;
|
||||
}
|
||||
|
||||
public void setEvictionMinute(int evictionMinute) {
|
||||
if (evictionMinute > 59 || evictionMinute < 0) throw new IllegalArgumentException("Must be between 0 and 59");
|
||||
this.evictionMinute = evictionMinute;
|
||||
getConfig().putSingle(EVICTION_MINUTE, Integer.toString(evictionMinute));
|
||||
}
|
||||
|
||||
public int getEvictionDay() {
|
||||
if (evictionDay < 0) {
|
||||
String str = getConfig().getFirst(EVICTION_DAY);
|
||||
if (str == null) return -1;
|
||||
evictionDay = Integer.valueOf(str);
|
||||
}
|
||||
return evictionDay;
|
||||
}
|
||||
|
||||
public void setEvictionDay(int evictionDay) {
|
||||
if (evictionDay > 7 || evictionDay < 1) throw new IllegalArgumentException("Must be between 1 and 7");
|
||||
this.evictionDay = evictionDay;
|
||||
getConfig().putSingle(EVICTION_DAY, Integer.toString(evictionDay));
|
||||
}
|
||||
|
||||
public long getCacheInvalidBefore() {
|
||||
if (cacheInvalidBefore < 0) {
|
||||
String str = getConfig().getFirst(CACHE_INVALID_BEFORE);
|
||||
if (str == null) return -1;
|
||||
cacheInvalidBefore = Long.valueOf(str);
|
||||
}
|
||||
return cacheInvalidBefore;
|
||||
}
|
||||
|
||||
public void setCacheInvalidBefore(long cacheInvalidBefore) {
|
||||
this.cacheInvalidBefore = cacheInvalidBefore;
|
||||
getConfig().putSingle(CACHE_INVALID_BEFORE, Long.toString(cacheInvalidBefore));
|
||||
}
|
||||
|
||||
public void setEnabled(boolean flag) {
|
||||
enabled = flag;
|
||||
getConfig().putSingle(ENABLED, Boolean.toString(flag));
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
if (enabled == null) {
|
||||
String val = getConfig().getFirst(ENABLED);
|
||||
if (val == null) {
|
||||
enabled = true;
|
||||
} else {
|
||||
enabled = Boolean.valueOf(val);
|
||||
}
|
||||
}
|
||||
return enabled;
|
||||
|
||||
}
|
||||
|
||||
public long getLifespan() {
|
||||
CachePolicy policy = getCachePolicy();
|
||||
long lifespan = -1;
|
||||
if (policy == null || policy == CachePolicy.DEFAULT) {
|
||||
lifespan = -1;
|
||||
} else if (policy == CacheableStorageProviderModel.CachePolicy.EVICT_DAILY) {
|
||||
if (getEvictionHour() > -1 && getEvictionMinute() > -1) {
|
||||
lifespan = dailyTimeout(getEvictionHour(), getEvictionMinute()) - Time.currentTimeMillis();
|
||||
}
|
||||
} else if (policy == CacheableStorageProviderModel.CachePolicy.EVICT_WEEKLY) {
|
||||
if (getEvictionDay() > 0 && getEvictionHour() > -1 && getEvictionMinute() > -1) {
|
||||
lifespan = weeklyTimeout(getEvictionDay(), getEvictionHour(), getEvictionMinute()) - Time.currentTimeMillis();
|
||||
}
|
||||
} else if (policy == CacheableStorageProviderModel.CachePolicy.MAX_LIFESPAN) {
|
||||
lifespan = getMaxLifespan();
|
||||
}
|
||||
return lifespan;
|
||||
}
|
||||
|
||||
public boolean shouldInvalidate(CachedObject cached) {
|
||||
boolean invalidate = false;
|
||||
if (!isEnabled()) {
|
||||
invalidate = true;
|
||||
} else {
|
||||
CacheableStorageProviderModel.CachePolicy policy = getCachePolicy();
|
||||
if (policy != null) {
|
||||
//String currentTime = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(Time.currentTimeMillis()));
|
||||
if (policy == CacheableStorageProviderModel.CachePolicy.NO_CACHE) {
|
||||
invalidate = true;
|
||||
} else if (cached.getCacheTimestamp() < getCacheInvalidBefore()) {
|
||||
invalidate = true;
|
||||
} else if (policy == CacheableStorageProviderModel.CachePolicy.MAX_LIFESPAN) {
|
||||
if (cached.getCacheTimestamp() + getMaxLifespan() < Time.currentTimeMillis()) {
|
||||
invalidate = true;
|
||||
}
|
||||
} else if (policy == CacheableStorageProviderModel.CachePolicy.EVICT_DAILY) {
|
||||
long dailyBoundary = dailyEvictionBoundary(getEvictionHour(), getEvictionMinute());
|
||||
if (cached.getCacheTimestamp() <= dailyBoundary) {
|
||||
invalidate = true;
|
||||
}
|
||||
} else if (policy == CacheableStorageProviderModel.CachePolicy.EVICT_WEEKLY) {
|
||||
int oneWeek = 7 * 24 * 60 * 60 * 1000;
|
||||
long weeklyTimeout = weeklyTimeout(getEvictionDay(), getEvictionHour(), getEvictionMinute());
|
||||
long lastTimeout = weeklyTimeout - oneWeek;
|
||||
//String timeout = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(weeklyTimeout));
|
||||
//String stamp = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(new Date(cached.getCacheTimestamp()));
|
||||
if (cached.getCacheTimestamp() <= lastTimeout) {
|
||||
invalidate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return invalidate;
|
||||
}
|
||||
|
||||
|
||||
public static long dailyTimeout(int hour, int minute) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
Calendar cal2 = Calendar.getInstance();
|
||||
cal.setTimeInMillis(Time.currentTimeMillis());
|
||||
cal2.setTimeInMillis(Time.currentTimeMillis());
|
||||
cal2.set(Calendar.HOUR_OF_DAY, hour);
|
||||
cal2.set(Calendar.MINUTE, minute);
|
||||
if (cal2.getTimeInMillis() < cal.getTimeInMillis()) {
|
||||
int add = (24 * 60 * 60 * 1000);
|
||||
cal.add(Calendar.MILLISECOND, add);
|
||||
} else {
|
||||
cal = cal2;
|
||||
}
|
||||
return cal.getTimeInMillis();
|
||||
}
|
||||
|
||||
public static long dailyEvictionBoundary(int hour, int minute) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTimeInMillis(Time.currentTimeMillis());
|
||||
cal.set(Calendar.HOUR_OF_DAY, hour);
|
||||
cal.set(Calendar.MINUTE, minute);
|
||||
if (cal.getTimeInMillis() > Time.currentTimeMillis()) {
|
||||
// if daily evict for today hasn't happened yet set boundary
|
||||
// to yesterday's time of eviction
|
||||
cal.add(Calendar.DAY_OF_YEAR, -1);
|
||||
}
|
||||
return cal.getTimeInMillis();
|
||||
}
|
||||
|
||||
public static long weeklyTimeout(int day, int hour, int minute) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
Calendar cal2 = Calendar.getInstance();
|
||||
cal.setTimeInMillis(Time.currentTimeMillis());
|
||||
cal2.setTimeInMillis(Time.currentTimeMillis());
|
||||
cal2.set(Calendar.HOUR_OF_DAY, hour);
|
||||
cal2.set(Calendar.MINUTE, minute);
|
||||
cal2.set(Calendar.DAY_OF_WEEK, day);
|
||||
if (cal2.getTimeInMillis() < cal.getTimeInMillis()) {
|
||||
int add = (7 * 24 * 60 * 60 * 1000);
|
||||
cal2.add(Calendar.MILLISECOND, add);
|
||||
}
|
||||
|
||||
return cal2.getTimeInMillis();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public enum CachePolicy {
|
||||
NO_CACHE,
|
||||
DEFAULT,
|
||||
EVICT_DAILY,
|
||||
EVICT_WEEKLY,
|
||||
MAX_LIFESPAN
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2022 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 org.keycloak.models.ClientProvider;
|
||||
import org.keycloak.models.ClientScopeProvider;
|
||||
import org.keycloak.models.GroupProvider;
|
||||
import org.keycloak.models.RoleProvider;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||
|
||||
public interface StoreManagers {
|
||||
|
||||
ClientProvider clientStorageManager();
|
||||
|
||||
ClientScopeProvider clientScopeStorageManager();
|
||||
|
||||
RoleProvider roleStorageManager();
|
||||
|
||||
GroupProvider groupStorageManager();
|
||||
|
||||
UserProvider userStorageManager();
|
||||
|
||||
UserProvider userLocalStorage();
|
||||
|
||||
UserFederatedStorageProvider userFederatedStorage();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* A class implementing this interface represents a user storage provider to Keycloak.
|
||||
* <p/>
|
||||
* This interface contains only very basic methods for manipulating users. However, the storage provider capabilities
|
||||
* are extended by implementing one or more of the following capability interfaces:
|
||||
* <ul>
|
||||
* <li>{@link org.keycloak.storage.user.UserLookupProvider UserLookupProvider} - Provide basic lookup methods. After implementing it is possible to login using users from the storage.</li>
|
||||
* <li>{@link org.keycloak.storage.user.UserQueryMethodsProvider UserQueryMethodsProvider} - Provide complex lookup methods. After implementing it is possible to manage users from admin console.</li>
|
||||
* <li>{@link org.keycloak.storage.user.UserCountMethodsProvider UserCountMethodsProvider} - Provide complex count methods. After implementing it is possible to leverage optimizations during querying for users.</li>
|
||||
* <li>{@link org.keycloak.storage.user.UserQueryProvider UserQueryProvider} - This interface is combined capability of {@code UserQueryMethodsProvider} and {@code UserCountMethodsProvider}.</li>
|
||||
* <li>{@link org.keycloak.storage.user.UserRegistrationProvider UserRegistrationProvider} - Provide methods for adding users. After implementing it is possible to store registered users in the storage.</li>
|
||||
* <li>{@link org.keycloak.storage.user.UserBulkUpdateProvider UserBulkUpdateProvider} - After implementing it is possible to perform bulk operations on all users from storage (for example, addition of a role to all users).</li>
|
||||
* <li>{@link org.keycloak.storage.user.ImportedUserValidation ImportedUserValidation} - Provider method for validating users within Keycloak local storage that are imported from the storage.</li>
|
||||
* <li>{@link org.keycloak.storage.user.ImportSynchronization ImportSynchronization} - Provider methods for synchronization of the storage with Keycloak local storage. After implementing it is possible to sync users in the Admin console.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserStorageProvider extends Provider {
|
||||
|
||||
|
||||
/**
|
||||
* Callback when a realm is removed. Implement this if, for example, you want to do some
|
||||
* cleanup in your user storage when a realm is removed
|
||||
*
|
||||
* @param realm
|
||||
*/
|
||||
default
|
||||
void preRemove(RealmModel realm) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when a group is removed. Allows you to do things like remove a user
|
||||
* group mapping in your external store if appropriate
|
||||
*
|
||||
* @param realm
|
||||
* @param group
|
||||
*/
|
||||
default
|
||||
void preRemove(RealmModel realm, GroupModel group) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when a role is removed. Allows you to do things like remove a user
|
||||
* role mapping in your external store if appropriate
|
||||
|
||||
* @param realm
|
||||
* @param role
|
||||
*/
|
||||
default
|
||||
void preRemove(RealmModel realm, RoleModel role) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional type that can be used by implementations to
|
||||
* describe edit mode of user storage
|
||||
*
|
||||
*/
|
||||
enum EditMode {
|
||||
/**
|
||||
* user storage is read-only
|
||||
*/
|
||||
READ_ONLY,
|
||||
/**
|
||||
* user storage is writable
|
||||
*
|
||||
*/
|
||||
WRITABLE,
|
||||
/**
|
||||
* updates to user are stored locally and not synced with user storage.
|
||||
*
|
||||
*/
|
||||
UNSYNCED
|
||||
}
|
||||
}
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.storage;
|
||||
|
||||
import 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;
|
||||
import org.keycloak.storage.user.ImportSynchronization;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserStorageProviderFactory<T extends UserStorageProvider> extends ComponentFactory<T, UserStorageProvider> {
|
||||
|
||||
|
||||
/**
|
||||
* called per Keycloak transaction.
|
||||
*
|
||||
* @param session
|
||||
* @param model
|
||||
* @return
|
||||
*/
|
||||
T create(KeycloakSession session, ComponentModel model);
|
||||
|
||||
/**
|
||||
* This is the name of the provider and will be showed in the admin console as an option.
|
||||
*
|
||||
* @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.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when UserStorageProviderModel is created. This allows you to do initialization of any additional configuration
|
||||
* you need to add. For example, you may be introspecting a database or ldap schema to automatically create mappings.
|
||||
*
|
||||
* @param session
|
||||
* @param realm
|
||||
* @param model
|
||||
*/
|
||||
@Override
|
||||
default void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* configuration properties that are common across all UserStorageProvider implementations
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
default
|
||||
List<ProviderConfigProperty> getCommonProviderConfigProperties() {
|
||||
return UserStorageProviderSpi.commonConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
default
|
||||
Map<String, Object> getTypeMetadata() {
|
||||
Map<String, Object> metadata = new HashMap<>();
|
||||
if (this instanceof ImportSynchronization) {
|
||||
metadata.put("synchronizable", true);
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.storage;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
|
||||
/**
|
||||
* Stored configuration of a User Storage provider instance.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
||||
*/
|
||||
public class UserStorageProviderModel extends CacheableStorageProviderModel {
|
||||
|
||||
public static final String IMPORT_ENABLED = "importEnabled";
|
||||
public static final String FULL_SYNC_PERIOD = "fullSyncPeriod";
|
||||
public static final String CHANGED_SYNC_PERIOD = "changedSyncPeriod";
|
||||
public static final String LAST_SYNC = "lastSync";
|
||||
|
||||
public UserStorageProviderModel() {
|
||||
setProviderType(UserStorageProvider.class.getName());
|
||||
}
|
||||
|
||||
public UserStorageProviderModel(ComponentModel copy) {
|
||||
super(copy);
|
||||
}
|
||||
|
||||
private transient Integer fullSyncPeriod;
|
||||
private transient Integer changedSyncPeriod;
|
||||
private transient Integer lastSync;
|
||||
private transient Boolean importEnabled;
|
||||
|
||||
public boolean isImportEnabled() {
|
||||
if (importEnabled == null) {
|
||||
String val = getConfig().getFirst(IMPORT_ENABLED);
|
||||
if (val == null) {
|
||||
importEnabled = true;
|
||||
} else {
|
||||
importEnabled = Boolean.valueOf(val);
|
||||
}
|
||||
}
|
||||
return importEnabled;
|
||||
|
||||
}
|
||||
|
||||
public void setImportEnabled(boolean flag) {
|
||||
importEnabled = flag;
|
||||
getConfig().putSingle(IMPORT_ENABLED, Boolean.toString(flag));
|
||||
}
|
||||
|
||||
|
||||
public int getFullSyncPeriod() {
|
||||
if (fullSyncPeriod == null) {
|
||||
String val = getConfig().getFirst(FULL_SYNC_PERIOD);
|
||||
if (val == null) {
|
||||
fullSyncPeriod = -1;
|
||||
} else {
|
||||
fullSyncPeriod = Integer.valueOf(val);
|
||||
}
|
||||
}
|
||||
return fullSyncPeriod;
|
||||
}
|
||||
|
||||
public void setFullSyncPeriod(int fullSyncPeriod) {
|
||||
this.fullSyncPeriod = fullSyncPeriod;
|
||||
getConfig().putSingle(FULL_SYNC_PERIOD, Integer.toString(fullSyncPeriod));
|
||||
}
|
||||
|
||||
public int getChangedSyncPeriod() {
|
||||
if (changedSyncPeriod == null) {
|
||||
String val = getConfig().getFirst(CHANGED_SYNC_PERIOD);
|
||||
if (val == null) {
|
||||
changedSyncPeriod = -1;
|
||||
} else {
|
||||
changedSyncPeriod = Integer.valueOf(val);
|
||||
}
|
||||
}
|
||||
return changedSyncPeriod;
|
||||
}
|
||||
|
||||
public void setChangedSyncPeriod(int changedSyncPeriod) {
|
||||
this.changedSyncPeriod = changedSyncPeriod;
|
||||
getConfig().putSingle(CHANGED_SYNC_PERIOD, Integer.toString(changedSyncPeriod));
|
||||
}
|
||||
|
||||
public int getLastSync() {
|
||||
if (lastSync == null) {
|
||||
String val = getConfig().getFirst(LAST_SYNC);
|
||||
if (val == null) {
|
||||
lastSync = 0;
|
||||
} else {
|
||||
lastSync = Integer.valueOf(val);
|
||||
}
|
||||
}
|
||||
return lastSync;
|
||||
}
|
||||
|
||||
public void setLastSync(int lastSync) {
|
||||
this.lastSync = lastSync;
|
||||
getConfig().putSingle(LAST_SYNC, Integer.toString(lastSync));
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.storage;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UserStorageProviderSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "storage";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return UserStorageProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return UserStorageProviderFactory.class;
|
||||
}
|
||||
|
||||
private static final List<ProviderConfigProperty> commonConfig;
|
||||
|
||||
static {
|
||||
List<ProviderConfigProperty> config = ProviderConfigurationBuilder.create()
|
||||
.property()
|
||||
.name("enabled").type(ProviderConfigProperty.BOOLEAN_TYPE).add()
|
||||
.property()
|
||||
.name("priority").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("fullSyncPeriod").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("changedSyncPeriod").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("lastSync").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("batchSizeForSync").type(ProviderConfigProperty.STRING_TYPE).add()
|
||||
.property()
|
||||
.name("importEnabled").type(ProviderConfigProperty.BOOLEAN_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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2022 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 org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.cache.UserCache;
|
||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||
|
||||
/**
|
||||
* @author Alexander Schwartz
|
||||
*/
|
||||
public class UserStorageUtil {
|
||||
|
||||
public static UserFederatedStorageProvider userFederatedStorage(KeycloakSession session) {
|
||||
return session.getProvider(UserFederatedStorageProvider.class);
|
||||
}
|
||||
|
||||
public static UserCache userCache(KeycloakSession session) {
|
||||
return session.getProvider(UserCache.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
/*
|
||||
* Copyright 2022 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.adapter;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModelDefaultMethods;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
import org.keycloak.storage.StorageId;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* This abstract class provides implementations for everything but getUsername(). getId() returns a default value
|
||||
* of "f:" + providerId + ":" + getUsername(). isEnabled() returns true. getRoleMappings() will return default roles.
|
||||
* getGroups() will return default groups.
|
||||
*
|
||||
* All other read methods return null, an empty collection, or false depending
|
||||
* on the type. All update methods throw a ReadOnlyException.
|
||||
*
|
||||
* Provider implementors should override the methods for attributes, properties, and mappings they support.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class AbstractUserAdapter extends UserModelDefaultMethods {
|
||||
protected KeycloakSession session;
|
||||
protected RealmModel realm;
|
||||
protected ComponentModel storageProviderModel;
|
||||
|
||||
public AbstractUserAdapter(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.storageProviderModel = storageProviderModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated User {@link #getRequiredActionsStream()}
|
||||
*/
|
||||
public Set<String> getRequiredActions() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getRequiredActionsStream() {
|
||||
return getRequiredActions().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String action) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String action) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(RequiredAction action) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(RequiredAction action) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group membership mappings that are managed by this storage provider
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected Set<GroupModel> getGroupsInternal() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the realm's default groups be appended to getGroups() call?
|
||||
* If your storage provider is not managing group mappings then it is recommended that
|
||||
* this method return true
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean appendDefaultGroups() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getGroupsStream()} instead
|
||||
*/
|
||||
public Set<GroupModel> getGroups() {
|
||||
Set<GroupModel> set = new HashSet<>();
|
||||
if (appendDefaultGroups()) set.addAll(realm.getDefaultGroupsStream().collect(Collectors.toSet()));
|
||||
set.addAll(getGroupsInternal());
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> getGroupsStream() {
|
||||
return getGroups().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinGroup(GroupModel group) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveGroup(GroupModel group) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
return RoleUtils.isMember(getGroups().stream(), group);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated Use {@link #getRealmRoleMappingsStream()} instead
|
||||
*/
|
||||
public Set<RoleModel> getRealmRoleMappings() {
|
||||
return getRoleMappings().stream().filter(RoleUtils::isRealmRole).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRealmRoleMappingsStream() {
|
||||
return getRealmRoleMappings().stream();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated Use {@link #getClientRoleMappingsStream(ClientModel)} instead
|
||||
*/
|
||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||
return getRoleMappings().stream().filter(r -> RoleUtils.isClientRole(r, app)).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getClientRoleMappingsStream(ClientModel app) {
|
||||
return getClientRoleMappings(app).stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
return RoleUtils.hasRole(getRoleMappings().stream(), role)
|
||||
|| RoleUtils.hasRoleFromGroup(getGroups().stream(), role, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantRole(RoleModel role) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the realm's default roles be appended to getRoleMappings() call?
|
||||
* If your storage provider is not managing all role mappings then it is recommended that
|
||||
* this method return true
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean appendDefaultRolesToRoleMappings() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Set<RoleModel> getRoleMappingsInternal() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated Use {@link #getRoleMappingsStream()} instead
|
||||
*/
|
||||
public Set<RoleModel> getRoleMappings() {
|
||||
Set<RoleModel> set = new HashSet<>();
|
||||
if (appendDefaultRolesToRoleMappings()) set.addAll(realm.getDefaultRole().getCompositesStream().collect(Collectors.toSet()));
|
||||
set.addAll(getRoleMappingsInternal());
|
||||
return set;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRoleMappingsStream() {
|
||||
return getRoleMappings().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRoleMapping(RoleModel role) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should not be overriden
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getFederationLink() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should not be overriden
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public void setFederationLink(String link) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should not be overriden
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should not be overriden
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public void setServiceAccountClientLink(String clientInternalId) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
protected StorageId storageId;
|
||||
|
||||
/**
|
||||
* Defaults to 'f:' + storageProvider.getId() + ':' + getUsername()
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
if (storageId == null) {
|
||||
storageId = new StorageId(storageProviderModel.getId(), getUsername());
|
||||
}
|
||||
return storageId.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
}
|
||||
|
||||
protected long created = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public Long getCreatedTimestamp() {
|
||||
return created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreatedTimestamp(Long timestamp) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
if (name.equals(UserModel.USERNAME)) {
|
||||
return getUsername();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||
attributes.add(UserModel.USERNAME, getUsername());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getAttributeStream(String)} instead
|
||||
*/
|
||||
public List<String> getAttribute(String name) {
|
||||
if (name.equals(UserModel.USERNAME)) {
|
||||
return Collections.singletonList(getUsername());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getAttributeStream(String name) {
|
||||
return getAttribute(name).stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirstName(String firstName) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastName(String lastName) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmail() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmail(String email) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmailVerified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmailVerified(boolean verified) {
|
||||
throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof UserModel)) return false;
|
||||
|
||||
UserModel that = (UserModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Streams} interface makes all collection-based methods in {@link AbstractUserAdapter} default by providing
|
||||
* implementations that delegate to the {@link Stream}-based variants instead of the other way around.
|
||||
* <p/>
|
||||
* It allows for implementations to focus on the {@link Stream}-based approach for processing sets of data and benefit
|
||||
* from the potential memory and performance optimizations of that approach.
|
||||
*/
|
||||
public abstract static class Streams extends AbstractUserAdapter implements UserModel {
|
||||
|
||||
public Streams(final KeycloakSession session, final RealmModel realm, final ComponentModel storageProviderModel) {
|
||||
super(session, realm, storageProviderModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRequiredActions() {
|
||||
return this.getRequiredActionsStream().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getRequiredActionsStream() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
return this.getAttributeStream(name).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getAttributeStream(String name) {
|
||||
if (name.equals(UserModel.USERNAME)) {
|
||||
return Stream.of(getUsername());
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
// group-related methods.
|
||||
|
||||
|
||||
@Override
|
||||
public Set<GroupModel> getGroups() {
|
||||
return this.getGroupsStream().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> getGroupsStream() {
|
||||
Stream<GroupModel> groups = getGroupsInternal().stream();
|
||||
if (appendDefaultGroups()) groups = Stream.concat(groups, realm.getDefaultGroupsStream());
|
||||
return groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
return RoleUtils.isMember(this.getGroupsStream(), group);
|
||||
}
|
||||
|
||||
// role-related methods.
|
||||
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmRoleMappings() {
|
||||
return this.getRealmRoleMappingsStream().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRealmRoleMappingsStream() {
|
||||
return getRoleMappingsStream().filter(RoleUtils::isRealmRole);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||
return this.getClientRoleMappingsStream(app).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getClientRoleMappingsStream(ClientModel app) {
|
||||
return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRoleMappings() {
|
||||
return this.getRoleMappingsStream().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRoleMappingsStream() {
|
||||
Stream<RoleModel> roleMappings = getRoleMappingsInternal().stream();
|
||||
if (appendDefaultRolesToRoleMappings()) return Stream.concat(roleMappings, realm.getDefaultRole().getCompositesStream());
|
||||
return roleMappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
return RoleUtils.hasRole(this.getRoleMappingsStream(), role)
|
||||
|| RoleUtils.hasRoleFromGroup(this.getGroupsStream(), role, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.adapter;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.credential.UserCredentialManager;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.SubjectCredentialManager;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModelDefaultMethods;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageUtil;
|
||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Assumes everything is managed by federated storage except for username. getId() returns a default value
|
||||
* of "f:" + providerId + ":" + getUsername(). UserModel properties like enabled, firstName, lastName, email, etc. are all
|
||||
* stored as attributes in federated storage.
|
||||
*
|
||||
* isEnabled() defaults to true if the ENABLED_ATTRIBUTE isn't set in federated storage
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class AbstractUserAdapterFederatedStorage extends UserModelDefaultMethods {
|
||||
public static String FIRST_NAME_ATTRIBUTE = "FIRST_NAME";
|
||||
public static String LAST_NAME_ATTRIBUTE = "LAST_NAME";
|
||||
public static String EMAIL_ATTRIBUTE = "EMAIL";
|
||||
public static String EMAIL_VERIFIED_ATTRIBUTE = "EMAIL_VERIFIED";
|
||||
public static String CREATED_TIMESTAMP_ATTRIBUTE = "CREATED_TIMESTAMP";
|
||||
public static String ENABLED_ATTRIBUTE = "ENABLED";
|
||||
|
||||
|
||||
protected KeycloakSession session;
|
||||
protected RealmModel realm;
|
||||
protected ComponentModel storageProviderModel;
|
||||
|
||||
public AbstractUserAdapterFederatedStorage(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.storageProviderModel = storageProviderModel;
|
||||
}
|
||||
|
||||
public UserFederatedStorageProvider getFederatedStorage() {
|
||||
return UserStorageUtil.userFederatedStorage(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getRequiredActionsStream() {
|
||||
return getFederatedStorage().getRequiredActionsStream(realm, this.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(String action) {
|
||||
getFederatedStorage().addRequiredAction(realm, this.getId(), action);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(String action) {
|
||||
getFederatedStorage().removeRequiredAction(realm, this.getId(), action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRequiredAction(RequiredAction action) {
|
||||
getFederatedStorage().addRequiredAction(realm, this.getId(), action.name());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRequiredAction(RequiredAction action) {
|
||||
getFederatedStorage().removeRequiredAction(realm, this.getId(), action.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group membership mappings that are managed by this storage provider
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected Set<GroupModel> getGroupsInternal() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the realm's default groups be appended to getGroups() call?
|
||||
* If your storage provider is not managing group mappings then it is recommended that
|
||||
* this method return true
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean appendDefaultGroups() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets groups from federated storage and automatically appends default groups of realm.
|
||||
* Also calls getGroupsInternal() method
|
||||
* to pull group membership from provider. Implementors can override that method
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public Stream<GroupModel> getGroupsStream() {
|
||||
Stream<GroupModel> groups = getFederatedStorage().getGroupsStream(realm, this.getId());
|
||||
if (appendDefaultGroups()) groups = Stream.concat(groups, realm.getDefaultGroupsStream());
|
||||
return Stream.concat(groups, getGroupsInternal().stream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinGroup(GroupModel group) {
|
||||
getFederatedStorage().joinGroup(realm, this.getId(), group);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveGroup(GroupModel group) {
|
||||
getFederatedStorage().leaveGroup(realm, this.getId(), group);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemberOf(GroupModel group) {
|
||||
return RoleUtils.isMember(getGroupsStream(), group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets role mappings from federated storage and automatically appends default roles.
|
||||
* Also calls getRoleMappingsInternal() method
|
||||
* to pull role mappings from provider. Implementors can override that method
|
||||
*/
|
||||
@Override
|
||||
public Stream<RoleModel> getRealmRoleMappingsStream() {
|
||||
return this.getRoleMappingsStream().filter(RoleUtils::isRealmRole);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets role mappings from federated storage and automatically appends default roles.
|
||||
* Also calls getRoleMappingsInternal() method
|
||||
* to pull role mappings from provider. Implementors can override that method
|
||||
*/
|
||||
@Override
|
||||
public Stream<RoleModel> getClientRoleMappingsStream(ClientModel app) {
|
||||
return getRoleMappingsStream().filter(r -> RoleUtils.isClientRole(r, app));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
return RoleUtils.hasRole(getRoleMappingsStream(), role)
|
||||
|| RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void grantRole(RoleModel role) {
|
||||
getFederatedStorage().grantRole(realm, this.getId(), role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the realm's default roles be appended to getRoleMappings() call?
|
||||
* If your storage provider is not managing all role mappings then it is recommended that
|
||||
* this method return true
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean appendDefaultRolesToRoleMappings() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Set<RoleModel> getRoleMappingsInternal() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets role mappings from federated storage and automatically appends default roles.
|
||||
* Also calls getRoleMappingsInternal() method
|
||||
* to pull role mappings from provider. Implementors can override that method
|
||||
*/
|
||||
@Override
|
||||
public Stream<RoleModel> getRoleMappingsStream() {
|
||||
Stream<RoleModel> roleMappings = getFederatedRoleMappingsStream();
|
||||
if (appendDefaultRolesToRoleMappings()) {
|
||||
roleMappings = Stream.concat(roleMappings, realm.getDefaultRole().getCompositesStream());
|
||||
}
|
||||
return Stream.concat(roleMappings, getRoleMappingsInternal().stream());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getFederatedRoleMappingsStream()} instead
|
||||
*/
|
||||
@Deprecated
|
||||
protected Set<RoleModel> getFederatedRoleMappings() {
|
||||
return getFederatedRoleMappingsStream().collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected Stream<RoleModel> getFederatedRoleMappingsStream() {
|
||||
return getFederatedStorage().getRoleMappingsStream(realm, this.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRoleMapping(RoleModel role) {
|
||||
getFederatedStorage().deleteRoleMapping(realm, this.getId(), role);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
String val = getFirstAttribute(ENABLED_ATTRIBUTE);
|
||||
if (val == null) return true;
|
||||
else return Boolean.valueOf(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
setSingleAttribute(ENABLED_ATTRIBUTE, Boolean.toString(enabled));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should not be overriden
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getFederationLink() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should not be overriden
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public void setFederationLink(String link) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should not be overriden
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should not be overriden
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public void setServiceAccountClientLink(String clientInternalId) {
|
||||
|
||||
}
|
||||
|
||||
protected StorageId storageId;
|
||||
|
||||
/**
|
||||
* Defaults to 'f:' + storageProvider.getId() + ':' + getUsername()
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
if (storageId == null) {
|
||||
storageId = new StorageId(storageProviderModel.getId(), getUsername());
|
||||
}
|
||||
return storageId.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCreatedTimestamp() {
|
||||
String val = getFirstAttribute(CREATED_TIMESTAMP_ATTRIBUTE);
|
||||
if (val == null) return null;
|
||||
else return Long.valueOf(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreatedTimestamp(Long timestamp) {
|
||||
if (timestamp == null) {
|
||||
setSingleAttribute(CREATED_TIMESTAMP_ATTRIBUTE, null);
|
||||
} else {
|
||||
setSingleAttribute(CREATED_TIMESTAMP_ATTRIBUTE, Long.toString(timestamp));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
if (UserModel.USERNAME.equals(name)) {
|
||||
setUsername(value);
|
||||
} else {
|
||||
getFederatedStorage().setSingleAttribute(realm, this.getId(), mapAttribute(name), value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
getFederatedStorage().removeAttribute(realm, this.getId(), name);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
if (UserModel.USERNAME.equals(name)) {
|
||||
setUsername((values != null && !values.isEmpty()) ? values.get(0) : null);
|
||||
} else {
|
||||
getFederatedStorage().setAttribute(realm, this.getId(), mapAttribute(name), values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
if (UserModel.USERNAME.equals(name)) {
|
||||
return getUsername();
|
||||
}
|
||||
return getFederatedStorage().getAttributes(realm, this.getId()).getFirst(mapAttribute(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
MultivaluedHashMap<String, String> attributes = getFederatedStorage().getAttributes(realm, this.getId());
|
||||
if (attributes == null) {
|
||||
attributes = new MultivaluedHashMap<>();
|
||||
}
|
||||
List<String> firstName = attributes.remove(FIRST_NAME_ATTRIBUTE);
|
||||
attributes.add(UserModel.FIRST_NAME, firstName != null && firstName.size() >= 1 ? firstName.get(0) : null);
|
||||
List<String> lastName = attributes.remove(LAST_NAME_ATTRIBUTE);
|
||||
attributes.add(UserModel.LAST_NAME, lastName != null && lastName.size() >= 1 ? lastName.get(0) : null);
|
||||
List<String> email = attributes.remove(EMAIL_ATTRIBUTE);
|
||||
attributes.add(UserModel.EMAIL, email != null && email.size() >= 1 ? email.get(0) : null);
|
||||
attributes.add(UserModel.USERNAME, getUsername());
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getAttributeStream(String name) {
|
||||
if (UserModel.USERNAME.equals(name)) {
|
||||
return Stream.of(getUsername());
|
||||
}
|
||||
List<String> result = getFederatedStorage().getAttributes(realm, this.getId()).get(mapAttribute(name));
|
||||
return (result == null) ? Stream.empty() : result.stream();
|
||||
}
|
||||
|
||||
protected String mapAttribute(String attributeName) {
|
||||
if (UserModel.FIRST_NAME.equals(attributeName)) {
|
||||
return FIRST_NAME_ATTRIBUTE;
|
||||
} else if (UserModel.LAST_NAME.equals(attributeName)) {
|
||||
return LAST_NAME_ATTRIBUTE;
|
||||
} else if (UserModel.EMAIL.equals(attributeName)) {
|
||||
return EMAIL_ATTRIBUTE;
|
||||
}
|
||||
return attributeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmailVerified() {
|
||||
String val = getFirstAttribute(EMAIL_VERIFIED_ATTRIBUTE);
|
||||
if (val == null) return false;
|
||||
else return Boolean.valueOf(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores as attribute in federated storage.
|
||||
* EMAIL_VERIFIED_ATTRIBUTE
|
||||
*
|
||||
* @param verified
|
||||
*/
|
||||
@Override
|
||||
public void setEmailVerified(boolean verified) {
|
||||
setSingleAttribute(EMAIL_VERIFIED_ATTRIBUTE, Boolean.toString(verified));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubjectCredentialManager credentialManager() {
|
||||
return new UserCredentialManager(session, realm, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || !(o instanceof UserModel)) return false;
|
||||
|
||||
UserModel that = (UserModel) o;
|
||||
return that.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This interface is no longer necessary; collection-based methods were removed from the parent interface
|
||||
* and therefore the parent interface can be used directly
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract static class Streams extends AbstractUserAdapterFederatedStorage implements UserModel {
|
||||
|
||||
public Streams(final KeycloakSession session, final RealmModel realm, final ComponentModel storageProviderModel) {
|
||||
super(session, realm, storageProviderModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.client;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* Base interface for components that want to provide an alternative storage mechanism for clients
|
||||
*
|
||||
* This is currently a private incomplete SPI. Please discuss on dev list if you want us to complete it or want to do the work yourself.
|
||||
* This work is described in KEYCLOAK-6408 JIRA issue.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface ClientStorageProvider extends Provider, ClientLookupProvider {
|
||||
|
||||
|
||||
/**
|
||||
* Callback when a realm is removed. Implement this if, for example, you want to do some
|
||||
* cleanup in your user storage when a realm is removed
|
||||
*
|
||||
* @param realm
|
||||
*/
|
||||
default
|
||||
void preRemove(RealmModel realm) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when a group is removed. Allows you to do things like remove a user
|
||||
* group mapping in your external store if appropriate
|
||||
*
|
||||
* @param realm
|
||||
* @param group
|
||||
*/
|
||||
default
|
||||
void preRemove(RealmModel realm, GroupModel group) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when a role is removed. Allows you to do things like remove a user
|
||||
* role mapping in your external store if appropriate
|
||||
|
||||
* @param realm
|
||||
* @param role
|
||||
*/
|
||||
default
|
||||
void preRemove(RealmModel realm, RoleModel role) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.storage.client;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.storage.CacheableStorageProviderModel;
|
||||
|
||||
/**
|
||||
* Stored configuration of a Client Storage provider instance.
|
||||
*
|
||||
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
|
||||
*/
|
||||
public class ClientStorageProviderModel extends CacheableStorageProviderModel {
|
||||
|
||||
public static final String ENABLED = "enabled";
|
||||
|
||||
public ClientStorageProviderModel() {
|
||||
setProviderType(ClientStorageProvider.class.getName());
|
||||
}
|
||||
|
||||
public ClientStorageProviderModel(ComponentModel copy) {
|
||||
super(copy);
|
||||
}
|
||||
|
||||
private transient Boolean enabled;
|
||||
|
||||
public void setEnabled(boolean flag) {
|
||||
enabled = flag;
|
||||
getConfig().putSingle(ENABLED, Boolean.toString(flag));
|
||||
}
|
||||
|
||||
|
||||
public boolean isEnabled() {
|
||||
if (enabled == null) {
|
||||
String val = getConfig().getFirst(ENABLED);
|
||||
if (val == null) {
|
||||
enabled = true;
|
||||
} else {
|
||||
enabled = Boolean.valueOf(val);
|
||||
}
|
||||
}
|
||||
return enabled;
|
||||
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserAttributeFederatedStorage {
|
||||
void setSingleAttribute(RealmModel realm, String userId, String name, String value);
|
||||
void setAttribute(RealmModel realm, String userId, String name, List<String> values);
|
||||
void removeAttribute(RealmModel realm, String userId, String name);
|
||||
MultivaluedHashMap<String, String> getAttributes(RealmModel realm, String userId);
|
||||
|
||||
/**
|
||||
* Searches for federated users that have an attribute with the specified {@code name} and {@code value}.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param name the attribute name.
|
||||
* @param value the attribute value.
|
||||
* @return a non-null {@link Stream} of users that match the search criteria.
|
||||
*/
|
||||
Stream<String> getUsersByUserAttributeStream(RealmModel realm, String name, String value);
|
||||
|
||||
/**
|
||||
* @deprecated This interface is no longer necessary; collection-based methods were removed from the parent interface
|
||||
* and therefore the parent interface can be used directly
|
||||
*/
|
||||
@Deprecated
|
||||
interface Streams extends UserAttributeFederatedStorage {
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserBrokerLinkFederatedStorage {
|
||||
String getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
|
||||
void addFederatedIdentity(RealmModel realm, String userId, FederatedIdentityModel socialLink);
|
||||
boolean removeFederatedIdentity(RealmModel realm, String userId, String socialProvider);
|
||||
void preRemove(RealmModel realm, IdentityProviderModel provider);
|
||||
void updateFederatedIdentity(RealmModel realm, String userId, FederatedIdentityModel federatedIdentityModel);
|
||||
|
||||
/**
|
||||
* Obtains the identities of the federated user identified by {@code userId}.
|
||||
*
|
||||
* @param userId the user identifier.
|
||||
* @param realm a reference to the realm.
|
||||
* @return a non-null {@link Stream} of federated identities associated with the user.
|
||||
*/
|
||||
Stream<FederatedIdentityModel> getFederatedIdentitiesStream(String userId, RealmModel realm);
|
||||
|
||||
FederatedIdentityModel getFederatedIdentity(String userId, String socialProvider, RealmModel realm);
|
||||
|
||||
/**
|
||||
* @deprecated This interface is no longer necessary; collection-based methods were removed from the parent interface
|
||||
* and therefore the parent interface can be used directly
|
||||
*/
|
||||
@Deprecated
|
||||
interface Streams extends UserBrokerLinkFederatedStorage {
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserConsentFederatedStorage {
|
||||
void addConsent(RealmModel realm, String userId, UserConsentModel consent);
|
||||
UserConsentModel getConsentByClient(RealmModel realm, String userId, String clientInternalId);
|
||||
|
||||
/**
|
||||
* Obtains the consents associated with the federated user identified by {@code userId}.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param userId the user identifier.
|
||||
* @return a non-null {@link Stream} of consents associated with the user.
|
||||
*/
|
||||
Stream<UserConsentModel> getConsentsStream(RealmModel realm, String userId);
|
||||
|
||||
void updateConsent(RealmModel realm, String userId, UserConsentModel consent);
|
||||
boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId);
|
||||
|
||||
/**
|
||||
* @deprecated This interface is no longer necessary, collection-based methods were removed from the parent interface
|
||||
* and therefore the parent interface can be used directly
|
||||
*/
|
||||
@Deprecated
|
||||
interface Streams extends UserConsentFederatedStorage {
|
||||
}
|
||||
}
|
||||
Executable
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserFederatedStorageProvider extends Provider,
|
||||
UserAttributeFederatedStorage,
|
||||
UserBrokerLinkFederatedStorage,
|
||||
UserConsentFederatedStorage,
|
||||
UserNotBeforeFederatedStorage,
|
||||
UserGroupMembershipFederatedStorage,
|
||||
UserRequiredActionsFederatedStorage,
|
||||
UserRoleMappingsFederatedStorage,
|
||||
UserFederatedUserCredentialStore {
|
||||
|
||||
/**
|
||||
* Obtains the ids of all federated users in the realm.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param first first result to return. Ignored if negative or {@code null}.
|
||||
* @param max maximum number of results to return. Ignored if negative or {@code null}.
|
||||
* @return a non-null {@link Stream} of federated user ids.
|
||||
*/
|
||||
Stream<String> getStoredUsersStream(RealmModel realm, Integer first, Integer max);
|
||||
|
||||
int getStoredUsersCount(RealmModel realm);
|
||||
|
||||
void preRemove(RealmModel realm);
|
||||
|
||||
void preRemove(RealmModel realm, GroupModel group);
|
||||
|
||||
void preRemove(RealmModel realm, RoleModel role);
|
||||
|
||||
void preRemove(RealmModel realm, ClientModel client);
|
||||
|
||||
void preRemove(ProtocolMapperModel protocolMapper);
|
||||
|
||||
void preRemove(ClientScopeModel clientScope);
|
||||
|
||||
void preRemove(RealmModel realm, UserModel user);
|
||||
|
||||
void preRemove(RealmModel realm, ComponentModel model);
|
||||
|
||||
/**
|
||||
* @deprecated This interface is no longer necessary; collection-based methods were removed from the parent interface
|
||||
* and therefore the parent interface can be used directly
|
||||
*/
|
||||
@Deprecated
|
||||
interface Streams extends UserFederatedStorageProvider,
|
||||
UserAttributeFederatedStorage.Streams,
|
||||
UserBrokerLinkFederatedStorage.Streams,
|
||||
UserConsentFederatedStorage.Streams,
|
||||
UserFederatedUserCredentialStore.Streams,
|
||||
UserGroupMembershipFederatedStorage.Streams,
|
||||
UserRequiredActionsFederatedStorage.Streams,
|
||||
UserRoleMappingsFederatedStorage.Streams {
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserFederatedStorageProviderFactory extends ProviderFactory<UserFederatedStorageProvider> {
|
||||
}
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UserFederatedStorageProviderSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "userFederatedStorage";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return UserFederatedStorageProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return UserFederatedStorageProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserFederatedUserCredentialStore extends Provider {
|
||||
void updateCredential(RealmModel realm, String userId, CredentialModel cred);
|
||||
CredentialModel createCredential(RealmModel realm, String userId, CredentialModel cred);
|
||||
boolean removeStoredCredential(RealmModel realm, String userId, String id);
|
||||
CredentialModel getStoredCredentialById(RealmModel realm, String userId, String id);
|
||||
|
||||
/**
|
||||
* Obtains the credentials associated with the federated user identified by {@code userId}.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param userId the user identifier.
|
||||
* @return a non-null {@link Stream} of credentials.
|
||||
*/
|
||||
Stream<CredentialModel> getStoredCredentialsStream(RealmModel realm, String userId);
|
||||
|
||||
/**
|
||||
* Obtains the credentials of type {@code type} that are associated with the federated user identified by {@code userId}.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param userId the user identifier.
|
||||
* @param type the credential type.
|
||||
* @return a non-null {@link Stream} of credentials.
|
||||
*/
|
||||
Stream<CredentialModel> getStoredCredentialsByTypeStream(RealmModel realm, String userId, String type);
|
||||
|
||||
CredentialModel getStoredCredentialByNameAndType(RealmModel realm, String userId, String name, String type);
|
||||
|
||||
/**
|
||||
* @deprecated This interface is no longer necessary; collection-based methods were removed from the parent interface
|
||||
* and therefore the parent interface can be used directly
|
||||
*/
|
||||
@Deprecated
|
||||
interface Streams extends UserFederatedUserCredentialStore {
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserGroupMembershipFederatedStorage {
|
||||
|
||||
/**
|
||||
* Obtains the groups associated with the federated user.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param userId the user identifier.
|
||||
* @return a non-null {@code Stream} of groups.
|
||||
*/
|
||||
Stream<GroupModel> getGroupsStream(RealmModel realm, String userId);
|
||||
|
||||
void joinGroup(RealmModel realm, String userId, GroupModel group);
|
||||
void leaveGroup(RealmModel realm, String userId, GroupModel group);
|
||||
|
||||
/**
|
||||
* Obtains the federated users that are members of the given {@code group} in the specified {@code realm}.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param group a reference to the group whose federated members are being searched.
|
||||
* @param firstResult first result to return. Ignored if negative or {@code null}.
|
||||
* @param max maximum number of results to return. Ignored if negative or {@code null}.
|
||||
* @return a non-null {@code Stream} of federated user ids that are members of the group in the realm.
|
||||
*/
|
||||
Stream<String> getMembershipStream(RealmModel realm, GroupModel group, Integer firstResult, Integer max);
|
||||
|
||||
/**
|
||||
* @deprecated This interface is no longer necessary; collection-based methods were removed from the parent interface
|
||||
* and therefore the parent interface can be used directly
|
||||
*/
|
||||
@Deprecated
|
||||
interface Streams extends UserGroupMembershipFederatedStorage {
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2017 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.federated;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface UserNotBeforeFederatedStorage {
|
||||
|
||||
void setNotBeforeForUser(RealmModel realm, String userId, int notBefore);
|
||||
int getNotBeforeOfUser(RealmModel realm, String userId);
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserRequiredActionsFederatedStorage {
|
||||
|
||||
/**
|
||||
* Obtains the names of required actions associated with the federated user identified by {@code userId}.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param userId the user identifier.
|
||||
* @return a non-null {@link Stream} of required action names.
|
||||
*/
|
||||
Stream<String> getRequiredActionsStream(RealmModel realm, String userId);
|
||||
|
||||
void addRequiredAction(RealmModel realm, String userId, String action);
|
||||
void removeRequiredAction(RealmModel realm, String userId, String action);
|
||||
|
||||
/**
|
||||
* @deprecated This interface is no longer necessary; collection-based methods were removed from the parent interface
|
||||
* and therefore the parent interface can be used directly
|
||||
*/
|
||||
@Deprecated
|
||||
interface Streams extends UserRequiredActionsFederatedStorage {
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.federated;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface UserRoleMappingsFederatedStorage {
|
||||
|
||||
void grantRole(RealmModel realm, String userId, RoleModel role);
|
||||
|
||||
/**
|
||||
* Obtains the roles associated with the federated user identified by {@code userId}.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param userId the user identifier.
|
||||
* @return a non-null {@code Stream} of roles.
|
||||
*/
|
||||
Stream<RoleModel> getRoleMappingsStream(RealmModel realm, String userId);
|
||||
|
||||
void deleteRoleMapping(RealmModel realm, String userId, RoleModel role);
|
||||
|
||||
/**
|
||||
* Obtains the federated users that are members of the given {@code role} in the specified {@code realm}.
|
||||
*
|
||||
* @param realm a reference to the realm.
|
||||
* @param role a reference to the role whose federated members are being searched.
|
||||
* @param firstResult first result to return. Ignored if negative or {@code null}.
|
||||
* @param max maximum number of results to return. Ignored if negative or {@code null}.
|
||||
* @return a non-null {@code Stream} of federated user ids that are members of the role in the realm.
|
||||
*/
|
||||
Stream<String> getRoleMembersStream(RealmModel realm, RoleModel role, Integer firstResult, Integer max);
|
||||
|
||||
/**
|
||||
* @deprecated This interface is no longer necessary; collection-based methods were removed from the parent interface
|
||||
* and therefore the parent interface can be used directly
|
||||
*/
|
||||
@Deprecated
|
||||
interface Streams extends UserRoleMappingsFederatedStorage {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 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.group;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
public interface GroupStorageProvider extends Provider, GroupLookupProvider {
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2020 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.group;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.storage.CacheableStorageProviderModel;
|
||||
|
||||
/**
|
||||
* Stored configuration of a Group Storage provider instance.
|
||||
*/
|
||||
public class GroupStorageProviderModel extends CacheableStorageProviderModel {
|
||||
|
||||
public GroupStorageProviderModel() {
|
||||
setProviderType(GroupStorageProvider.class.getName());
|
||||
}
|
||||
|
||||
public GroupStorageProviderModel(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,57 @@
|
||||
/*
|
||||
* Copyright 2020 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.role;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.storage.CacheableStorageProviderModel;
|
||||
|
||||
/**
|
||||
* Stored configuration of a Role Storage provider instance.
|
||||
*/
|
||||
public class RoleStorageProviderModel extends CacheableStorageProviderModel {
|
||||
|
||||
public RoleStorageProviderModel() {
|
||||
setProviderType(RoleStorageProvider.class.getName());
|
||||
}
|
||||
|
||||
public RoleStorageProviderModel(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,36 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.user;
|
||||
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
* This is an optional capability interface that is intended to be implemented by any
|
||||
* {@link org.keycloak.storage.UserStorageProvider UserStorageProvider} that supports syncing users to keycloak local
|
||||
* storage. You must implement this interface if you want to be able to use sync functionality within the Admin console.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface ImportSynchronization {
|
||||
SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model);
|
||||
SynchronizationResult syncSince(Date lastSync, KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.storage.user;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* This is an optional capability interface that is intended to be implemented by any
|
||||
* {@link org.keycloak.storage.UserStorageProvider UserStorageProvider} that supports validating users. You must
|
||||
* implement this interface if your storage imports users into the Keycloak local storage and you want to sync these
|
||||
* users with your storage. The idea is, that whenever keycloak queries users imported from your storage, the method
|
||||
* {@link #validate(RealmModel, UserModel) validate()} is called and if it returns null, the user is removed from
|
||||
* local storage and reloaded from your storage by corresponding method.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface ImportedUserValidation {
|
||||
/**
|
||||
* If this method returns null, then the user in local storage will be removed
|
||||
*
|
||||
* @param realm
|
||||
* @param user
|
||||
* @return null if user no longer valid
|
||||
*/
|
||||
UserModel validate(RealmModel realm, UserModel user);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2020 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.utils;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.executors.ExecutorsProvider;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
|
||||
|
||||
/**
|
||||
* Utility class for general helper methods used across the keycloak-services.
|
||||
*/
|
||||
public class ServicesUtils {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ServicesUtils.class);
|
||||
|
||||
public static <T, R> Function<? super T,? extends Stream<? extends R>> timeBound(KeycloakSession session,
|
||||
long timeout,
|
||||
Function<T, ? extends Stream<R>> func) {
|
||||
ExecutorService executor = session.getProvider(ExecutorsProvider.class).getExecutor("storage-provider-threads");
|
||||
return p -> {
|
||||
// We are running another thread here, which serves as a time checking thread. When timeout is hit, the time
|
||||
// checking thread will send interrupted flag to main thread, which can cause interruption of func execution.
|
||||
// To support interruption func implementation should react to interrupt flag.
|
||||
// If func doesn't check the interrupted flag, the execution won't be interrupted and can take more time
|
||||
// than the threshold given by timeout variable
|
||||
Future<?> timeCheckingThread = executor.submit(timeWarningRunnable(timeout, Thread.currentThread()));
|
||||
try {
|
||||
// We cannot run func in different than main thread, because main thread have, for example, EntityManager
|
||||
// transaction context. If we run any operation on EntityManager in a different thread, it will fail
|
||||
// with a transaction doesn't exist error
|
||||
return func.apply(p);
|
||||
} finally {
|
||||
timeCheckingThread.cancel(true);
|
||||
|
||||
if (Thread.interrupted()) {
|
||||
logger.warnf("Execution with object [%s] exceeded specified time limit %d. %s", p, timeout, getShortStackTrace());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T, R> Function<? super T, R> timeBoundOne(KeycloakSession session,
|
||||
long timeout,
|
||||
Function<T, R> func) {
|
||||
ExecutorService executor = session.getProvider(ExecutorsProvider.class).getExecutor("storage-provider-threads");
|
||||
return p -> {
|
||||
// We are running another thread here, which serves as a time checking thread. When timeout is hit, the time
|
||||
// checking thread will send interrupted flag to main thread, which can cause interruption of func execution.
|
||||
// To support interruption func implementation should react to interrupt flag.
|
||||
// If func doesn't check the interrupted flag, the execution won't be interrupted and can take more time
|
||||
// than the threshold given by timeout variable
|
||||
Future<?> warningThreadFuture = executor.submit(timeWarningRunnable(timeout, Thread.currentThread()));
|
||||
try {
|
||||
// We cannot run func in different than main thread, because main thread have, for example, EntityManager
|
||||
// transaction context. If we run any operation on EntityManager in a different thread, it will fail
|
||||
// with a transaction doesn't exist error
|
||||
return func.apply(p);
|
||||
} finally {
|
||||
warningThreadFuture.cancel(true);
|
||||
|
||||
if (Thread.interrupted()) {
|
||||
logger.warnf("Execution with object [%s] exceeded specified time limit %d. %s", p, timeout, getShortStackTrace());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Consumer<? super T> consumeWithTimeBound(KeycloakSession session,
|
||||
long timeout,
|
||||
Consumer<T> func) {
|
||||
ExecutorService executor = session.getProvider(ExecutorsProvider.class).getExecutor("storage-provider-threads");
|
||||
return p -> {
|
||||
// We are running another thread here, which serves as a time checking thread. When timeout is hit, the time
|
||||
// checking thread will send interrupted flag to main thread, which can cause interruption of func execution.
|
||||
// To support interruption func implementation should react to interrupt flag.
|
||||
// If func doesn't check the interrupted flag, the execution won't be interrupted and can take more time
|
||||
// than the threshold given by timeout variable
|
||||
Future<?> warningThreadFuture = executor.submit(timeWarningRunnable(timeout, Thread.currentThread()));
|
||||
try {
|
||||
// We cannot run func in different than main thread, because main thread have, for example, EntityManager
|
||||
// transaction context. If we run any operation on EntityManager in a different thread, it will fail
|
||||
// with a transaction doesn't exist error
|
||||
func.accept(p);
|
||||
} finally {
|
||||
warningThreadFuture.cancel(true);
|
||||
|
||||
if (Thread.interrupted()) {
|
||||
logger.warnf("Execution with object [%s] exceeded specified time limit %d. %s", p, timeout, getShortStackTrace());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Runnable timeWarningRunnable(long timeout, Thread mainThread) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(timeout);
|
||||
} catch (InterruptedException exception) {
|
||||
return; // Do not interrupt if warning thread was interrupted (== main thread finished execution in time)
|
||||
}
|
||||
|
||||
mainThread.interrupt();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright 2022 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.storage.UserStorageProviderSpi
|
||||
org.keycloak.storage.federated.UserFederatedStorageProviderSpi
|
||||
Reference in New Issue
Block a user