Merge pull request #3599 from patriot1burke/master
User Storage SPI Javadocs and examples
This commit is contained in:
commit
257aba4331
34 changed files with 820 additions and 514 deletions
|
@ -21,6 +21,8 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Replaces any ${} strings with their corresponding system property.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
|
@ -32,7 +34,7 @@ public final class EnvUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Replaces any ${} strings with their corresponding environent variable.
|
||||
* Replaces any ${} strings with their corresponding system property.
|
||||
*
|
||||
* @param val
|
||||
* @return
|
||||
|
|
|
@ -10,4 +10,5 @@ Login and go to the User Federation tab and you should now see your deployed pro
|
|||
Add the provider, save it, then any new user you create will be stored and in the custom store you implemented. You
|
||||
can modify the example and hot deploy it using the above maven command again.
|
||||
|
||||
This example uses the built in in-memory datasource that comes with keycloak: ExampleDS.
|
||||
This example uses the built in in-memory datasource that comes with keycloak: ExampleDS. NOTE!!! You'll have
|
||||
to change this to be an xa datasource for this to work.
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
http://java.sun.com/xml/ns/persistence
|
||||
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
||||
<persistence-unit name="user-storage-jpa-example" transaction-type="JTA">
|
||||
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
|
||||
<jta-data-source>java:jboss/datasources/KeycloakDS</jta-data-source>
|
||||
|
||||
<class>org.keycloak.examples.storage.user.UserEntity</class>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Example User Federation Provider
|
||||
Example User Storage Provider
|
||||
===================================================
|
||||
|
||||
This is an example of user storage backed by a simple properties file. This properties file only contains username/password
|
||||
|
@ -7,13 +7,10 @@ key pairs. To deploy this provider you must have Keycloak running in standalone
|
|||
mvn clean install wildfly:deploy
|
||||
|
||||
|
||||
The "readonly-property-file" provider is hardcoded to look within the users.properties file embeded in the deployment jar
|
||||
THere is one user 'tbrady' with a password of 'superbowl'
|
||||
|
||||
The ClasspathPropertiesStorageProvider is an example of a readonly provider. If you go to the Users/Federation
|
||||
page of the admin console you will see this provider listed under "classpath-properties. To configure this provider you
|
||||
specify a classpath to a properties file in the "path" field of the admin page for this plugin. This example includes
|
||||
a "test-users.properties" within the JAR that you can use as the variable.
|
||||
The "writeable-property-file" provider can be configured to point to a property file on disk. It uses federated
|
||||
storage to augment the property file with any other information the user wants.
|
||||
|
||||
The FilePropertiesStorageProvider is an example of a writable provider. It synchronizes changes made to
|
||||
username and password with the properties file. If you go to the Users/Federation page of the admin console you will
|
||||
see this provider listed under "file-properties". To configure this provider you specify a fully qualified file path to
|
||||
a properties file in the "path" field of the admin page for this plugin.
|
||||
Our developer guide walks through the implementation of both of these providers.
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.federation.properties;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.storage.UserStorageProviderFactory;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class BasePropertiesStorageFactory<T extends BasePropertiesStorageProvider> implements UserStorageProviderFactory<T> {
|
||||
protected ConcurrentHashMap<String, Properties> files = new ConcurrentHashMap<String, Properties>();
|
||||
|
||||
|
||||
@Override
|
||||
public T create(KeycloakSession session, ComponentModel model) {
|
||||
// first get the path to our properties file from the stored configuration of this provider instance.
|
||||
String path = model.getConfig().getFirst("path");
|
||||
if (path == null) {
|
||||
throw new IllegalStateException("Path attribute not configured for provider");
|
||||
}
|
||||
// see if we already loaded the config file
|
||||
Properties props = files.get(path);
|
||||
if (props != null) return (T)createProvider(session, new UserStorageProviderModel(model), props);
|
||||
|
||||
|
||||
props = new Properties();
|
||||
InputStream is = getPropertiesFileStream(path);
|
||||
if (is != null) {
|
||||
try {
|
||||
props.load(is);
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
// remember the properties file for next time
|
||||
files.put(path, props);
|
||||
return (T)createProvider(session, new UserStorageProviderModel(model), props);
|
||||
}
|
||||
|
||||
protected abstract InputStream getPropertiesFileStream(String path);
|
||||
|
||||
protected abstract BasePropertiesStorageProvider createProvider(KeycloakSession session, UserStorageProviderModel model, Properties props);
|
||||
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.federation.properties;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ClasspathPropertiesStorageFactory extends BasePropertiesStorageFactory<ClasspathPropertiesStorageProvider> {
|
||||
|
||||
public static final String PROVIDER_NAME = "classpath-properties";
|
||||
protected static final List<ProviderConfigProperty> configProperties;
|
||||
|
||||
static {
|
||||
configProperties = ProviderConfigurationBuilder.create()
|
||||
.property().name("path")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.label("Classpath")
|
||||
.helpText("Classpath of properties file")
|
||||
.add().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BasePropertiesStorageProvider createProvider(KeycloakSession session, UserStorageProviderModel model, Properties props) {
|
||||
return new ClasspathPropertiesStorageProvider(session, model, props);
|
||||
}
|
||||
|
||||
protected InputStream getPropertiesFileStream(String path) {
|
||||
InputStream is = getClass().getClassLoader().getResourceAsStream(path);
|
||||
if (is == null) {
|
||||
throw new IllegalStateException("Path not found for properties file");
|
||||
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_NAME;
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.federation.properties;
|
||||
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialInputUpdater;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
import org.keycloak.storage.adapter.AbstractUserAdapter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Creates a read-only user. Implements CredentialInputUpdater so that passwords cannot be updated
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ClasspathPropertiesStorageProvider extends BasePropertiesStorageProvider implements CredentialInputUpdater {
|
||||
|
||||
public ClasspathPropertiesStorageProvider(KeycloakSession session, UserStorageProviderModel model, Properties properties) {
|
||||
super(session, model, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UserModel createAdapter(RealmModel realm, String username) {
|
||||
return new AbstractUserAdapter(session, realm, model) {
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (input.getType().equals(CredentialModel.PASSWORD)) throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.federation.properties;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class FilePropertiesStorageFactory extends BasePropertiesStorageFactory<FilePropertiesStorageProvider> {
|
||||
|
||||
public static final String PROVIDER_NAME = "file-properties";
|
||||
protected static final List<ProviderConfigProperty> configProperties;
|
||||
|
||||
static {
|
||||
configProperties = ProviderConfigurationBuilder.create()
|
||||
.property().name("path")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.label("Path")
|
||||
.helpText("File path to properties file")
|
||||
.add().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BasePropertiesStorageProvider createProvider(KeycloakSession session, UserStorageProviderModel model, Properties props) {
|
||||
return new FilePropertiesStorageProvider(session, props, model);
|
||||
}
|
||||
protected InputStream getPropertiesFileStream(String path) {
|
||||
try {
|
||||
return new FileInputStream(path);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Name of the provider. This will show up under the "Add Provider" select box on the Federation page in the
|
||||
* admin console
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_NAME;
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.federation.properties;
|
||||
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialInputUpdater;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
|
||||
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class FilePropertiesStorageProvider extends BasePropertiesStorageProvider implements
|
||||
UserRegistrationProvider,
|
||||
CredentialInputUpdater
|
||||
{
|
||||
|
||||
public FilePropertiesStorageProvider(KeycloakSession session, Properties properties, UserStorageProviderModel model) {
|
||||
super(session, model, properties);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
String path = model.getConfig().getFirst("path");
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(path);
|
||||
properties.store(fos, "");
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeUser(RealmModel realm, UserModel user) {
|
||||
synchronized (properties) {
|
||||
if (properties.remove(user.getUsername()) == null) return false;
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UserModel createAdapter(RealmModel realm, String username) {
|
||||
return new AbstractUserAdapterFederatedStorage(session, realm, model) {
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
throw new ReadOnlyException("Cannot edit username");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String username) {
|
||||
synchronized (properties) {
|
||||
properties.setProperty(username, UNSET_PASSWORD);
|
||||
save();
|
||||
}
|
||||
return createAdapter(realm, username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!(input instanceof UserCredentialModel)) return false;
|
||||
UserCredentialModel cred = (UserCredentialModel)input;
|
||||
if (!cred.getType().equals(CredentialModel.PASSWORD)) return false;
|
||||
synchronized (properties) {
|
||||
properties.setProperty(user.getUsername(), cred.getValue());
|
||||
save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||
if (!credentialType.equals(CredentialModel.PASSWORD)) return;
|
||||
synchronized (properties) {
|
||||
properties.setProperty(user.getUsername(), UNSET_PASSWORD);
|
||||
save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Set<String> disableableTypes = new HashSet<>();
|
||||
|
||||
static {
|
||||
disableableTypes.add(CredentialModel.PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
|
||||
|
||||
return disableableTypes;
|
||||
}
|
||||
}
|
|
@ -15,56 +15,52 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.examples.federation.properties;
|
||||
package org.keycloak.examples.userstorage.readonly;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialInputUpdater;
|
||||
import org.keycloak.credential.CredentialInputValidator;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
import org.keycloak.storage.adapter.AbstractUserAdapter;
|
||||
import org.keycloak.storage.user.UserLookupProvider;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class BasePropertiesStorageProvider implements
|
||||
public class PropertyFileUserStorageProvider implements
|
||||
UserStorageProvider,
|
||||
UserLookupProvider,
|
||||
CredentialInputValidator
|
||||
CredentialInputValidator,
|
||||
CredentialInputUpdater
|
||||
{
|
||||
public static final String UNSET_PASSWORD="#$!-UNSET-PASSWORD";
|
||||
|
||||
protected KeycloakSession session;
|
||||
protected Properties properties;
|
||||
protected UserStorageProviderModel model;
|
||||
protected ComponentModel model;
|
||||
// map of loaded users in this transaction
|
||||
protected Map<String, UserModel> loadedUsers = new HashMap<>();
|
||||
|
||||
public BasePropertiesStorageProvider(KeycloakSession session, UserStorageProviderModel model, Properties properties) {
|
||||
public PropertyFileUserStorageProvider(KeycloakSession session, ComponentModel model, Properties properties) {
|
||||
this.session = session;
|
||||
this.model = model;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public KeycloakSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
// UserLookupProvider methods
|
||||
|
||||
@Override
|
||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||
|
@ -79,6 +75,15 @@ public abstract class BasePropertiesStorageProvider implements
|
|||
return adapter;
|
||||
}
|
||||
|
||||
protected UserModel createAdapter(RealmModel realm, String username) {
|
||||
return new AbstractUserAdapter(session, realm, model) {
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserById(String id, RealmModel realm) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
|
@ -86,44 +91,18 @@ public abstract class BasePropertiesStorageProvider implements
|
|||
return getUserByUsername(username, realm);
|
||||
}
|
||||
|
||||
protected abstract UserModel createAdapter(RealmModel realm, String username);
|
||||
|
||||
@Override
|
||||
public UserModel getUserByEmail(String email, RealmModel realm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm) {
|
||||
// complete We don't care about the realm being removed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, RoleModel role) {
|
||||
// complete we dont'care if a role is removed
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRemove(RealmModel realm, GroupModel group) {
|
||||
// complete we dont'care if a role is removed
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
|
||||
|
||||
UserCredentialModel cred = (UserCredentialModel)input;
|
||||
String password = properties.getProperty(user.getUsername());
|
||||
if (password == null || password.equals(UNSET_PASSWORD)) return false;
|
||||
return password.equals(cred.getValue());
|
||||
}
|
||||
// CredentialInputValidator methods
|
||||
|
||||
@Override
|
||||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
||||
String password = properties.getProperty(user.getUsername());
|
||||
return credentialType.equals(CredentialModel.PASSWORD) && password != null && !password.equals(UNSET_PASSWORD);
|
||||
return credentialType.equals(CredentialModel.PASSWORD) && password != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -131,6 +110,36 @@ public abstract class BasePropertiesStorageProvider implements
|
|||
return credentialType.equals(CredentialModel.PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
|
||||
|
||||
UserCredentialModel cred = (UserCredentialModel)input;
|
||||
String password = properties.getProperty(user.getUsername());
|
||||
if (password == null) return false;
|
||||
return password.equals(cred.getValue());
|
||||
}
|
||||
|
||||
// CredentialInputUpdater methods
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (input.getType().equals(CredentialModel.PASSWORD)) throw new ReadOnlyException("user is read only for this update");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.examples.userstorage.readonly;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.storage.UserStorageProviderFactory;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PropertyFileUserStorageProviderFactory implements UserStorageProviderFactory<PropertyFileUserStorageProvider> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PropertyFileUserStorageProviderFactory.class);
|
||||
|
||||
public static final String PROVIDER_NAME = "readonly-property-file";
|
||||
|
||||
protected Properties properties = new Properties();
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_NAME;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
InputStream is = getClass().getClassLoader().getResourceAsStream("/users.properties");
|
||||
|
||||
if (is == null) {
|
||||
logger.warn("Could not find users.properties in classpath");
|
||||
} else {
|
||||
try {
|
||||
properties.load(is);
|
||||
} catch (IOException ex) {
|
||||
logger.error("Failed to load users.properties file", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyFileUserStorageProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new PropertyFileUserStorageProvider(session, model, properties);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* 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.examples.userstorage.writeable;
|
||||
|
||||
import org.keycloak.common.util.EnvUtil;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialInputUpdater;
|
||||
import org.keycloak.credential.CredentialInputValidator;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
|
||||
import org.keycloak.storage.user.UserLookupProvider;
|
||||
import org.keycloak.storage.user.UserQueryProvider;
|
||||
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PropertyFileUserStorageProvider implements
|
||||
UserStorageProvider,
|
||||
UserLookupProvider,
|
||||
CredentialInputValidator,
|
||||
CredentialInputUpdater,
|
||||
UserRegistrationProvider,
|
||||
UserQueryProvider {
|
||||
|
||||
|
||||
public static final String UNSET_PASSWORD="#$!-UNSET-PASSWORD";
|
||||
|
||||
protected KeycloakSession session;
|
||||
protected Properties properties;
|
||||
protected ComponentModel model;
|
||||
// map of loaded users in this transaction
|
||||
protected Map<String, UserModel> loadedUsers = new HashMap<>();
|
||||
|
||||
public PropertyFileUserStorageProvider(KeycloakSession session, ComponentModel model, Properties properties) {
|
||||
this.session = session;
|
||||
this.model = model;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
// UserLookupProvider methods
|
||||
|
||||
@Override
|
||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||
UserModel adapter = loadedUsers.get(username);
|
||||
if (adapter == null) {
|
||||
String password = properties.getProperty(username);
|
||||
if (password != null) {
|
||||
adapter = createAdapter(realm, username);
|
||||
loadedUsers.put(username, adapter);
|
||||
}
|
||||
}
|
||||
return adapter;
|
||||
}
|
||||
|
||||
protected UserModel createAdapter(RealmModel realm, String username) {
|
||||
return new AbstractUserAdapterFederatedStorage(session, realm, model) {
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
String pw = (String)properties.remove(username);
|
||||
if (pw != null) {
|
||||
properties.put(username, pw);
|
||||
save();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserById(String id, RealmModel realm) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
String username = storageId.getExternalId();
|
||||
return getUserByUsername(username, realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByEmail(String email, RealmModel realm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// UserQueryProvider methods
|
||||
|
||||
@Override
|
||||
public int getUsersCount(RealmModel realm) {
|
||||
return properties.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm) {
|
||||
return getUsers(realm, 0, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
|
||||
List<UserModel> users = new LinkedList<>();
|
||||
int i = 0;
|
||||
for (Object obj : properties.keySet()) {
|
||||
if (i++ < firstResult) continue;
|
||||
String username = (String)obj;
|
||||
UserModel user = getUserByUsername(username, realm);
|
||||
users.add(user);
|
||||
if (users.size() >= maxResults) break;
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
// UserQueryProvider method implementations
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(String search, RealmModel realm) {
|
||||
return searchForUser(search, realm, 0, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
List<UserModel> users = new LinkedList<>();
|
||||
int i = 0;
|
||||
for (Object obj : properties.keySet()) {
|
||||
String username = (String)obj;
|
||||
if (!username.contains(search)) continue;
|
||||
if (i++ < firstResult) continue;
|
||||
UserModel user = getUserByUsername(username, realm);
|
||||
users.add(user);
|
||||
if (users.size() >= maxResults) break;
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm) {
|
||||
return searchForUser(params, realm, 0, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
|
||||
// only support searching by username
|
||||
String usernameSearchString = params.get("username");
|
||||
if (usernameSearchString == null) return Collections.EMPTY_LIST;
|
||||
return searchForUser(usernameSearchString, realm, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
// runtime automatically handles querying UserFederatedStorage
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
|
||||
// runtime automatically handles querying UserFederatedStorage
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
|
||||
// runtime automatically handles querying UserFederatedStorage
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
|
||||
// UserRegistrationProvider method implementations
|
||||
|
||||
public void save() {
|
||||
String path = model.getConfig().getFirst("path");
|
||||
path = EnvUtil.replace(path);
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(path);
|
||||
properties.store(fos, "");
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String username) {
|
||||
synchronized (properties) {
|
||||
properties.setProperty(username, UNSET_PASSWORD);
|
||||
save();
|
||||
}
|
||||
return createAdapter(realm, username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeUser(RealmModel realm, UserModel user) {
|
||||
synchronized (properties) {
|
||||
if (properties.remove(user.getUsername()) == null) return false;
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// CredentialInputValidator methods
|
||||
|
||||
@Override
|
||||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
||||
String password = properties.getProperty(user.getUsername());
|
||||
return credentialType.equals(CredentialModel.PASSWORD) && password != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCredentialType(String credentialType) {
|
||||
return credentialType.equals(CredentialModel.PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
|
||||
|
||||
UserCredentialModel cred = (UserCredentialModel)input;
|
||||
String password = properties.getProperty(user.getUsername());
|
||||
if (password == null || UNSET_PASSWORD.equals(password)) return false;
|
||||
return password.equals(cred.getValue());
|
||||
}
|
||||
|
||||
// CredentialInputUpdater methods
|
||||
|
||||
@Override
|
||||
public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!(input instanceof UserCredentialModel)) return false;
|
||||
if (!input.getType().equals(CredentialModel.PASSWORD)) return false;
|
||||
UserCredentialModel cred = (UserCredentialModel)input;
|
||||
synchronized (properties) {
|
||||
properties.setProperty(user.getUsername(), cred.getValue());
|
||||
save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
|
||||
if (!credentialType.equals(CredentialModel.PASSWORD)) return;
|
||||
synchronized (properties) {
|
||||
properties.setProperty(user.getUsername(), UNSET_PASSWORD);
|
||||
save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Set<String> disableableTypes = new HashSet<>();
|
||||
|
||||
static {
|
||||
disableableTypes.add(CredentialModel.PASSWORD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) {
|
||||
|
||||
return disableableTypes;
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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.examples.userstorage.writeable;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.util.EnvUtil;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.storage.UserStorageProviderFactory;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PropertyFileUserStorageProviderFactory implements UserStorageProviderFactory<PropertyFileUserStorageProvider> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PropertyFileUserStorageProviderFactory.class);
|
||||
|
||||
public static final String PROVIDER_NAME = "writeable-property-file";
|
||||
|
||||
protected static final List<ProviderConfigProperty> configMetadata;
|
||||
|
||||
static {
|
||||
configMetadata = ProviderConfigurationBuilder.create()
|
||||
.property().name("path")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.label("Path")
|
||||
.defaultValue("${jboss.server.config.dir}/example-users.properties")
|
||||
.helpText("File path to properties file")
|
||||
.add().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
|
||||
String fp = config.getConfig().getFirst("path");
|
||||
if (fp == null) throw new ComponentValidationException("user property file does not exist");
|
||||
fp = EnvUtil.replace(fp);
|
||||
File file = new File(fp);
|
||||
if (!file.exists()) {
|
||||
throw new ComponentValidationException("user property file does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyFileUserStorageProvider create(KeycloakSession session, ComponentModel model) {
|
||||
String path = model.getConfig().getFirst("path");
|
||||
path = EnvUtil.replace(path);
|
||||
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
InputStream is = new FileInputStream(path);
|
||||
props.load(is);
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return new PropertyFileUserStorageProvider(session, model, props);
|
||||
}
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
org.keycloak.examples.federation.properties.ClasspathPropertiesStorageFactory
|
||||
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
|
||||
org.keycloak.examples.userstorage.readonly.PropertyFileUserStorageProviderFactory
|
||||
org.keycloak.examples.userstorage.writeable.PropertyFileUserStorageProviderFactory
|
|
@ -1,20 +0,0 @@
|
|||
#
|
||||
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
#
|
||||
#Sun Aug 03 10:52:57 EDT 2014
|
||||
belichick=superbowl
|
|
@ -25,7 +25,6 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RoleUtils;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
|
|
|
@ -1810,6 +1810,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
|||
em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate();
|
||||
em.flush();
|
||||
setConfig(component, c);
|
||||
ComponentUtil.notifyUpdated(session, this, component);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1706,6 +1706,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
}
|
||||
}
|
||||
updateRealm();
|
||||
ComponentUtil.notifyUpdated(session, this, model);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -89,5 +89,9 @@ public class ComponentUtil {
|
|||
ComponentFactory factory = getComponentFactory(session, model);
|
||||
factory.onCreate(session, realm, model);
|
||||
}
|
||||
public static void notifyUpdated(KeycloakSession session, RealmModel realm, ComponentModel model) {
|
||||
ComponentFactory factory = getComponentFactory(session, model);
|
||||
factory.onUpdate(session, realm, model);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,13 +39,22 @@ public interface ComponentFactory<CreatedType, ProviderType extends Provider> ex
|
|||
return null;
|
||||
}
|
||||
|
||||
void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException;
|
||||
default
|
||||
void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
default
|
||||
void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
|
||||
|
||||
}
|
||||
|
||||
default
|
||||
void onUpdate(KeycloakSession session, RealmModel realm, ComponentModel model) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* These are config properties that are common across all implementation of this component type
|
||||
*
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package org.keycloak.credential;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
|
|
|
@ -22,6 +22,10 @@ import org.keycloak.models.UserModel;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implentations of this interface can validate CredentialInput, i.e. verify a password.
|
||||
* UserStorageProviders and CredentialProviders can implement this interface.
|
||||
*
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
|
|
|
@ -22,6 +22,8 @@ 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 $
|
||||
*/
|
||||
|
@ -39,5 +41,10 @@ public interface UserCache extends UserProvider {
|
|||
* @param realm
|
||||
*/
|
||||
void evict(RealmModel realm);
|
||||
|
||||
/**
|
||||
* Clear cache entirely.
|
||||
*
|
||||
*/
|
||||
void clear();
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Delegation pattern. Used to proxy UserModel implementations.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
|
@ -21,24 +21,32 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
* Configuration property metadata. Used to render generic configuration pages for Keycloak extensions in the admin console.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ProviderConfigProperty {
|
||||
public static final String BOOLEAN_TYPE="boolean";
|
||||
public static final String STRING_TYPE="String";
|
||||
|
||||
// Possibility to configure multiple String values of any value (something like "redirect_uris" for clients)
|
||||
/**
|
||||
* Possibility to configure multiple String values of any value (something like "redirect_uris" for clients)
|
||||
*/
|
||||
public static final String MULTIVALUED_STRING_TYPE="MultivaluedString";
|
||||
|
||||
public static final String SCRIPT_TYPE="Script";
|
||||
public static final String FILE_TYPE="File";
|
||||
public static final String ROLE_TYPE="Role";
|
||||
|
||||
// Possibility to configure single String value, which needs to be chosen from the list of predefined values (HTML select)
|
||||
/**
|
||||
* Possibility to configure single String value, which needs to be chosen from the list of predefined values (HTML select)
|
||||
*/
|
||||
public static final String LIST_TYPE="List";
|
||||
|
||||
// Possibility to configure multiple String values, which needs to be chosen from the list of predefined values (HTML select with multiple)
|
||||
/**
|
||||
* Possibility to configure multiple String values, which needs to be chosen from the list of predefined values (HTML select with multiple)
|
||||
*/
|
||||
public static final String MULTIVALUED_LIST_TYPE="MultivaluedList";
|
||||
|
||||
public static final String CLIENT_LIST_TYPE="ClientList";
|
||||
|
@ -77,6 +85,11 @@ public class ProviderConfigProperty {
|
|||
this.secret = secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the config variable stored in the database
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
@ -85,6 +98,11 @@ public class ProviderConfigProperty {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label shown in the admin console when configuring the variable
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
@ -93,6 +111,12 @@ public class ProviderConfigProperty {
|
|||
this.label = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of the variable. i.e. boolean, string etc. See the constants declared in this class for what your choices
|
||||
* are.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
@ -101,6 +125,11 @@ public class ProviderConfigProperty {
|
|||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default value for the variable
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Object getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
@ -109,6 +138,11 @@ public class ProviderConfigProperty {
|
|||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* For list types, this is a list of choices to choose from.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<String> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
@ -117,6 +151,11 @@ public class ProviderConfigProperty {
|
|||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Help text that will be displayed in the admin console tooltip
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getHelpText() {
|
||||
return helpText;
|
||||
}
|
||||
|
@ -125,6 +164,12 @@ public class ProviderConfigProperty {
|
|||
this.helpText = helpText;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, this variable is only writeable. It will never be viewable. This is important for things like
|
||||
* passwords in which you never want to display them on the screen.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Builds a list of ProviderConfigProperty instances.
|
||||
*
|
||||
*
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ProviderConfigurationBuilder {
|
||||
|
@ -58,6 +61,11 @@ public class ProviderConfigurationBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the list.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<ProviderConfigProperty> build() {
|
||||
return properties;
|
||||
}
|
||||
|
@ -77,42 +85,94 @@ public class ProviderConfigurationBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label that will be shown for this configuration property in the admin console
|
||||
*
|
||||
* @param label
|
||||
* @return
|
||||
*/
|
||||
public ProviderConfigPropertyBuilder label(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Help text that will be shown for this configuration property in the admin console
|
||||
* when you hover over the tooltip
|
||||
*
|
||||
* @param helpText
|
||||
* @return
|
||||
*/
|
||||
public ProviderConfigPropertyBuilder helpText(String helpText) {
|
||||
this.helpText = helpText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Property type. i.e. boolean, string.
|
||||
* @see ProviderConfigProperty
|
||||
*
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
public ProviderConfigPropertyBuilder type(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default value that will be shown when configuring this property for the first time
|
||||
*
|
||||
* @param defaultValue
|
||||
* @return
|
||||
*/
|
||||
public ProviderConfigPropertyBuilder defaultValue(Object defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If configuring a list type, these are the options you can choose from.
|
||||
*
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
public ProviderConfigPropertyBuilder options(String... options) {
|
||||
this.options = Arrays.asList(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If configuring a list type, these are the options you can choose from.
|
||||
*
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
public ProviderConfigPropertyBuilder options(List<String> options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If turned on, this property is only writable and never readable.
|
||||
* This is useful for things like passwords where you never want an admin
|
||||
* to be able to see what the password is.
|
||||
*
|
||||
* @param secret
|
||||
* @return
|
||||
*/
|
||||
public ProviderConfigPropertyBuilder secret(boolean secret) {
|
||||
this.secret = secret;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the current property, and start building the next one
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProviderConfigurationBuilder add() {
|
||||
ProviderConfigProperty property = new ProviderConfigProperty();
|
||||
property.setName(name);
|
||||
|
|
|
@ -28,9 +28,40 @@ import org.keycloak.provider.Provider;
|
|||
public interface UserStorageProvider extends Provider {
|
||||
|
||||
|
||||
void preRemove(RealmModel realm);
|
||||
void preRemove(RealmModel realm, GroupModel group);
|
||||
void preRemove(RealmModel realm, RoleModel role);
|
||||
/**
|
||||
* 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
|
||||
|
|
|
@ -40,7 +40,7 @@ import java.util.Set;
|
|||
* 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
|
||||
* 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 $
|
||||
|
@ -116,6 +116,14 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||
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
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<GroupModel> getGroups() {
|
||||
Set<GroupModel> set = new HashSet<>();
|
||||
|
@ -143,6 +151,14 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||
return RoleUtils.isMember(roles, 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
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<RoleModel> getRealmRoleMappings() {
|
||||
Set<RoleModel> roleMappings = getRoleMappings();
|
||||
|
@ -157,6 +173,14 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||
return realmRoles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||
Set<RoleModel> roleMappings = getRoleMappings();
|
||||
|
@ -202,6 +226,13 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<RoleModel> getRoleMappings() {
|
||||
Set<RoleModel> set = new HashSet<>();
|
||||
|
@ -343,6 +374,12 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||
return getFirstAttribute(FIRST_NAME_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores as attribute in federated storage.
|
||||
* FIRST_NAME_ATTRIBUTE
|
||||
*
|
||||
* @param firstName
|
||||
*/
|
||||
@Override
|
||||
public void setFirstName(String firstName) {
|
||||
setSingleAttribute(FIRST_NAME_ATTRIBUTE, firstName);
|
||||
|
@ -354,6 +391,12 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||
return getFirstAttribute(LAST_NAME_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores as attribute in federated storage.
|
||||
* LAST_NAME_ATTRIBUTE
|
||||
*
|
||||
* @param lastName
|
||||
*/
|
||||
@Override
|
||||
public void setLastName(String lastName) {
|
||||
setSingleAttribute(LAST_NAME_ATTRIBUTE, lastName);
|
||||
|
@ -365,6 +408,12 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||
return getFirstAttribute(EMAIL_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores as attribute in federated storage.
|
||||
* EMAIL_ATTRIBUTE
|
||||
*
|
||||
* @param email
|
||||
*/
|
||||
@Override
|
||||
public void setEmail(String email) {
|
||||
setSingleAttribute(EMAIL_ATTRIBUTE, email);
|
||||
|
@ -378,6 +427,12 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||
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));
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.keycloak.models.UserModel;
|
|||
*/
|
||||
public interface ImportedUserValidation {
|
||||
/**
|
||||
* If this method returns null, then the user storage in local storage will be removed
|
||||
* If this method returns null, then the user in local storage will be removed
|
||||
*
|
||||
* @param realm
|
||||
* @param user
|
||||
|
|
|
@ -20,6 +20,9 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* Optional capability interface implemented by UserStorageProviders. This interface is required
|
||||
* if you want the UserStorageProvider to support basic login capabilities.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
|
|
|
@ -24,6 +24,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Optional capability interface implemented by UserStorageProviders.
|
||||
* Defines complex queries that are used to locate one or more users. You must implement this interface
|
||||
* if you want to view and manager users from the administration console.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
|
@ -99,10 +103,47 @@ public interface UserQueryProvider {
|
|||
*/
|
||||
List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults);
|
||||
|
||||
/**
|
||||
* Get users that belong to a specific group. Implementations do not have to search in UserFederatedStorageProvider
|
||||
* as this is done automatically.
|
||||
*
|
||||
* @see org.keycloak.storage.federated.UserFederatedStorageProvider
|
||||
*
|
||||
* @param realm
|
||||
* @param group
|
||||
* @param firstResult
|
||||
* @param maxResults
|
||||
* @return
|
||||
*/
|
||||
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
|
||||
|
||||
/**
|
||||
* Get users that belong to a specific group. Implementations do not have to search in UserFederatedStorageProvider
|
||||
* as this is done automatically.
|
||||
*
|
||||
* @see org.keycloak.storage.federated.UserFederatedStorageProvider
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param realm
|
||||
* @param group
|
||||
* @return
|
||||
*/
|
||||
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group);
|
||||
|
||||
/**
|
||||
* Search for users that have a specific attribute with a specific value.
|
||||
* Implementations do not have to search in UserFederatedStorageProvider
|
||||
* as this is done automatically.
|
||||
*
|
||||
* @see org.keycloak.storage.federated.UserFederatedStorageProvider
|
||||
*
|
||||
|
||||
// Searching by UserModel.attribute (not property)
|
||||
*
|
||||
* @param attrName
|
||||
* @param attrValue
|
||||
* @param realm
|
||||
* @return
|
||||
*/
|
||||
List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* Optional capability interface implemented by UserStorageProviders.
|
||||
* Implement this interface if your provider supports adding and removing users.
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
|
|
|
@ -285,7 +285,9 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||
protected List<UserModel> importValidation(RealmModel realm, List<UserModel> users) {
|
||||
List<UserModel> tmp = new LinkedList<>();
|
||||
for (UserModel user : users) {
|
||||
tmp.add(importValidation(realm, user));
|
||||
UserModel model = importValidation(realm, user);
|
||||
if (model == null) continue;
|
||||
tmp.add(model);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue