Merge pull request #3599 from patriot1burke/master
User Storage SPI Javadocs and examples
This commit is contained in:
commit
257aba4331
34 files changed
+820
-514
No files matched your search
@@ -21,6 +21,8 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Replaces any ${} strings with their corresponding system property.
|
||||||
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @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
|
* @param val
|
||||||
* @return
|
* @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
|
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.
|
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
|
||||||
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
||||||
<persistence-unit name="user-storage-jpa-example" transaction-type="JTA">
|
<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>
|
<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
|
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
|
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
|
The "writeable-property-file" provider can be configured to point to a property file on disk. It uses federated
|
||||||
page of the admin console you will see this provider listed under "classpath-properties. To configure this provider you
|
storage to augment the property file with any other information the user wants.
|
||||||
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.
|
Our developer guide walks through the implementation of both of these providers.
|
||||||
|
|
||||||
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.
|
|
||||||
-69
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
-72
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-72
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-77
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-129
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+54
-45
@@ -15,56 +15,52 @@
|
|||||||
* limitations under the License.
|
* 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.CredentialInput;
|
||||||
|
import org.keycloak.credential.CredentialInputUpdater;
|
||||||
import org.keycloak.credential.CredentialInputValidator;
|
import org.keycloak.credential.CredentialInputValidator;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.models.GroupModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.storage.ReadOnlyException;
|
||||||
import org.keycloak.storage.StorageId;
|
import org.keycloak.storage.StorageId;
|
||||||
import org.keycloak.storage.UserStorageProvider;
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
import org.keycloak.storage.UserStorageProviderModel;
|
import org.keycloak.storage.adapter.AbstractUserAdapter;
|
||||||
import org.keycloak.storage.user.UserLookupProvider;
|
import org.keycloak.storage.user.UserLookupProvider;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public abstract class BasePropertiesStorageProvider implements
|
public class PropertyFileUserStorageProvider implements
|
||||||
UserStorageProvider,
|
UserStorageProvider,
|
||||||
UserLookupProvider,
|
UserLookupProvider,
|
||||||
CredentialInputValidator
|
CredentialInputValidator,
|
||||||
|
CredentialInputUpdater
|
||||||
{
|
{
|
||||||
public static final String UNSET_PASSWORD="#$!-UNSET-PASSWORD";
|
|
||||||
|
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
protected Properties properties;
|
protected Properties properties;
|
||||||
protected UserStorageProviderModel model;
|
protected ComponentModel model;
|
||||||
// map of loaded users in this transaction
|
// map of loaded users in this transaction
|
||||||
protected Map<String, UserModel> loadedUsers = new HashMap<>();
|
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.session = session;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeycloakSession getSession() {
|
// UserLookupProvider methods
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Properties getProperties() {
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||||
@@ -79,6 +75,15 @@ public abstract class BasePropertiesStorageProvider implements
|
|||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected UserModel createAdapter(RealmModel realm, String username) {
|
||||||
|
return new AbstractUserAdapter(session, realm, model) {
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserById(String id, RealmModel realm) {
|
public UserModel getUserById(String id, RealmModel realm) {
|
||||||
StorageId storageId = new StorageId(id);
|
StorageId storageId = new StorageId(id);
|
||||||
@@ -86,44 +91,18 @@ public abstract class BasePropertiesStorageProvider implements
|
|||||||
return getUserByUsername(username, realm);
|
return getUserByUsername(username, realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract UserModel createAdapter(RealmModel realm, String username);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUserByEmail(String email, RealmModel realm) {
|
public UserModel getUserByEmail(String email, RealmModel realm) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRemove(RealmModel realm) {
|
|
||||||
// complete We don't care about the realm being removed
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
// CredentialInputValidator methods
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
||||||
String password = properties.getProperty(user.getUsername());
|
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
|
@Override
|
||||||
@@ -131,6 +110,36 @@ public abstract class BasePropertiesStorageProvider implements
|
|||||||
return credentialType.equals(CredentialModel.PASSWORD);
|
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
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
+69
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+299
@@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+99
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
org.keycloak.examples.federation.properties.ClasspathPropertiesStorageFactory
|
org.keycloak.examples.userstorage.readonly.PropertyFileUserStorageProviderFactory
|
||||||
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
|
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
|
|
||||||
File renamed without changes.
-1
@@ -25,7 +25,6 @@ import org.keycloak.models.RealmModel;
|
|||||||
import org.keycloak.models.RoleContainerModel;
|
import org.keycloak.models.RoleContainerModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.models.utils.RoleUtils;
|
import org.keycloak.models.utils.RoleUtils;
|
||||||
import org.keycloak.models.utils.UserModelDelegate;
|
import org.keycloak.models.utils.UserModelDelegate;
|
||||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
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.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate();
|
||||||
em.flush();
|
em.flush();
|
||||||
setConfig(component, c);
|
setConfig(component, c);
|
||||||
|
ComponentUtil.notifyUpdated(session, this, component);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -1706,6 +1706,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateRealm();
|
updateRealm();
|
||||||
|
ComponentUtil.notifyUpdated(session, this, model);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,5 +89,9 @@ public class ComponentUtil {
|
|||||||
ComponentFactory factory = getComponentFactory(session, model);
|
ComponentFactory factory = getComponentFactory(session, model);
|
||||||
factory.onCreate(session, realm, 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException;
|
default
|
||||||
|
void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
default
|
default
|
||||||
void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
|
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
|
* These are config properties that are common across all implementation of this component type
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
package org.keycloak.credential;
|
package org.keycloak.credential;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ import org.keycloak.models.UserModel;
|
|||||||
import java.util.List;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import org.keycloak.models.UserModel;
|
|||||||
import org.keycloak.models.UserProvider;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@@ -39,5 +41,10 @@ public interface UserCache extends UserProvider {
|
|||||||
* @param realm
|
* @param realm
|
||||||
*/
|
*/
|
||||||
void evict(RealmModel realm);
|
void evict(RealmModel realm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear cache entirely.
|
||||||
|
*
|
||||||
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
}
|
}
|
||||||
+2
@@ -27,6 +27,8 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Delegation pattern. Used to proxy UserModel implementations.
|
||||||
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@@ -21,24 +21,32 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* Configuration property metadata. Used to render generic configuration pages for Keycloak extensions in the admin console.
|
||||||
* @version $Revision: 1 $
|
*
|
||||||
*/
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
public class ProviderConfigProperty {
|
public class ProviderConfigProperty {
|
||||||
public static final String BOOLEAN_TYPE="boolean";
|
public static final String BOOLEAN_TYPE="boolean";
|
||||||
public static final String STRING_TYPE="String";
|
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 MULTIVALUED_STRING_TYPE="MultivaluedString";
|
||||||
|
|
||||||
public static final String SCRIPT_TYPE="Script";
|
public static final String SCRIPT_TYPE="Script";
|
||||||
public static final String FILE_TYPE="File";
|
public static final String FILE_TYPE="File";
|
||||||
public static final String ROLE_TYPE="Role";
|
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";
|
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 MULTIVALUED_LIST_TYPE="MultivaluedList";
|
||||||
|
|
||||||
public static final String CLIENT_LIST_TYPE="ClientList";
|
public static final String CLIENT_LIST_TYPE="ClientList";
|
||||||
@@ -77,6 +85,11 @@ public class ProviderConfigProperty {
|
|||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the config variable stored in the database
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -85,6 +98,11 @@ public class ProviderConfigProperty {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label shown in the admin console when configuring the variable
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public String getLabel() {
|
public String getLabel() {
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
@@ -93,6 +111,12 @@ public class ProviderConfigProperty {
|
|||||||
this.label = label;
|
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() {
|
public String getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -101,6 +125,11 @@ public class ProviderConfigProperty {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default value for the variable
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public Object getDefaultValue() {
|
public Object getDefaultValue() {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
@@ -109,6 +138,11 @@ public class ProviderConfigProperty {
|
|||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For list types, this is a list of choices to choose from.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public List<String> getOptions() {
|
public List<String> getOptions() {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
@@ -117,6 +151,11 @@ public class ProviderConfigProperty {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Help text that will be displayed in the admin console tooltip
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public String getHelpText() {
|
public String getHelpText() {
|
||||||
return helpText;
|
return helpText;
|
||||||
}
|
}
|
||||||
@@ -125,6 +164,12 @@ public class ProviderConfigProperty {
|
|||||||
this.helpText = helpText;
|
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() {
|
public boolean isSecret() {
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Builds a list of ProviderConfigProperty instances.
|
||||||
|
*
|
||||||
|
*
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class ProviderConfigurationBuilder {
|
public class ProviderConfigurationBuilder {
|
||||||
@@ -58,6 +61,11 @@ public class ProviderConfigurationBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the list.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public List<ProviderConfigProperty> build() {
|
public List<ProviderConfigProperty> build() {
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
@@ -77,42 +85,94 @@ public class ProviderConfigurationBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label that will be shown for this configuration property in the admin console
|
||||||
|
*
|
||||||
|
* @param label
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public ProviderConfigPropertyBuilder label(String label) {
|
public ProviderConfigPropertyBuilder label(String label) {
|
||||||
this.label = label;
|
this.label = label;
|
||||||
return this;
|
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) {
|
public ProviderConfigPropertyBuilder helpText(String helpText) {
|
||||||
this.helpText = helpText;
|
this.helpText = helpText;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property type. i.e. boolean, string.
|
||||||
|
* @see ProviderConfigProperty
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public ProviderConfigPropertyBuilder type(String type) {
|
public ProviderConfigPropertyBuilder type(String type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default value that will be shown when configuring this property for the first time
|
||||||
|
*
|
||||||
|
* @param defaultValue
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public ProviderConfigPropertyBuilder defaultValue(Object defaultValue) {
|
public ProviderConfigPropertyBuilder defaultValue(Object defaultValue) {
|
||||||
this.defaultValue = defaultValue;
|
this.defaultValue = defaultValue;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If configuring a list type, these are the options you can choose from.
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public ProviderConfigPropertyBuilder options(String... options) {
|
public ProviderConfigPropertyBuilder options(String... options) {
|
||||||
this.options = Arrays.asList(options);
|
this.options = Arrays.asList(options);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If configuring a list type, these are the options you can choose from.
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public ProviderConfigPropertyBuilder options(List<String> options) {
|
public ProviderConfigPropertyBuilder options(List<String> options) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
return this;
|
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) {
|
public ProviderConfigPropertyBuilder secret(boolean secret) {
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the current property, and start building the next one
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public ProviderConfigurationBuilder add() {
|
public ProviderConfigurationBuilder add() {
|
||||||
ProviderConfigProperty property = new ProviderConfigProperty();
|
ProviderConfigProperty property = new ProviderConfigProperty();
|
||||||
property.setName(name);
|
property.setName(name);
|
||||||
|
|||||||
@@ -28,9 +28,40 @@ import org.keycloak.provider.Provider;
|
|||||||
public interface UserStorageProvider extends Provider {
|
public interface UserStorageProvider extends Provider {
|
||||||
|
|
||||||
|
|
||||||
void preRemove(RealmModel realm);
|
/**
|
||||||
void preRemove(RealmModel realm, GroupModel group);
|
* Callback when a realm is removed. Implement this if, for example, you want to do some
|
||||||
void preRemove(RealmModel realm, RoleModel role);
|
* 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
|
* Optional type that can be used by implementations to
|
||||||
|
|||||||
+56
-1
@@ -40,7 +40,7 @@ import java.util.Set;
|
|||||||
* of "f:" + providerId + ":" + getUsername(). UserModel properties like enabled, firstName, lastName, email, etc. are all
|
* of "f:" + providerId + ":" + getUsername(). UserModel properties like enabled, firstName, lastName, email, etc. are all
|
||||||
* stored as attributes in federated storage.
|
* 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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
@@ -116,6 +116,14 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||||||
return true;
|
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
|
@Override
|
||||||
public Set<GroupModel> getGroups() {
|
public Set<GroupModel> getGroups() {
|
||||||
Set<GroupModel> set = new HashSet<>();
|
Set<GroupModel> set = new HashSet<>();
|
||||||
@@ -143,6 +151,14 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||||||
return RoleUtils.isMember(roles, group);
|
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
|
@Override
|
||||||
public Set<RoleModel> getRealmRoleMappings() {
|
public Set<RoleModel> getRealmRoleMappings() {
|
||||||
Set<RoleModel> roleMappings = getRoleMappings();
|
Set<RoleModel> roleMappings = getRoleMappings();
|
||||||
@@ -157,6 +173,14 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||||||
return realmRoles;
|
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
|
@Override
|
||||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||||
Set<RoleModel> roleMappings = getRoleMappings();
|
Set<RoleModel> roleMappings = getRoleMappings();
|
||||||
@@ -202,6 +226,13 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||||||
return Collections.EMPTY_SET;
|
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
|
@Override
|
||||||
public Set<RoleModel> getRoleMappings() {
|
public Set<RoleModel> getRoleMappings() {
|
||||||
Set<RoleModel> set = new HashSet<>();
|
Set<RoleModel> set = new HashSet<>();
|
||||||
@@ -343,6 +374,12 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||||||
return getFirstAttribute(FIRST_NAME_ATTRIBUTE);
|
return getFirstAttribute(FIRST_NAME_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores as attribute in federated storage.
|
||||||
|
* FIRST_NAME_ATTRIBUTE
|
||||||
|
*
|
||||||
|
* @param firstName
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setFirstName(String firstName) {
|
public void setFirstName(String firstName) {
|
||||||
setSingleAttribute(FIRST_NAME_ATTRIBUTE, firstName);
|
setSingleAttribute(FIRST_NAME_ATTRIBUTE, firstName);
|
||||||
@@ -354,6 +391,12 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||||||
return getFirstAttribute(LAST_NAME_ATTRIBUTE);
|
return getFirstAttribute(LAST_NAME_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores as attribute in federated storage.
|
||||||
|
* LAST_NAME_ATTRIBUTE
|
||||||
|
*
|
||||||
|
* @param lastName
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setLastName(String lastName) {
|
public void setLastName(String lastName) {
|
||||||
setSingleAttribute(LAST_NAME_ATTRIBUTE, lastName);
|
setSingleAttribute(LAST_NAME_ATTRIBUTE, lastName);
|
||||||
@@ -365,6 +408,12 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||||||
return getFirstAttribute(EMAIL_ATTRIBUTE);
|
return getFirstAttribute(EMAIL_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores as attribute in federated storage.
|
||||||
|
* EMAIL_ATTRIBUTE
|
||||||
|
*
|
||||||
|
* @param email
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setEmail(String email) {
|
public void setEmail(String email) {
|
||||||
setSingleAttribute(EMAIL_ATTRIBUTE, email);
|
setSingleAttribute(EMAIL_ATTRIBUTE, email);
|
||||||
@@ -378,6 +427,12 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
|||||||
else return Boolean.valueOf(val);
|
else return Boolean.valueOf(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores as attribute in federated storage.
|
||||||
|
* EMAIL_VERIFIED_ATTRIBUTE
|
||||||
|
*
|
||||||
|
* @param verified
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setEmailVerified(boolean verified) {
|
public void setEmailVerified(boolean verified) {
|
||||||
setSingleAttribute(EMAIL_VERIFIED_ATTRIBUTE, Boolean.toString(verified));
|
setSingleAttribute(EMAIL_VERIFIED_ATTRIBUTE, Boolean.toString(verified));
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import org.keycloak.models.UserModel;
|
|||||||
*/
|
*/
|
||||||
public interface ImportedUserValidation {
|
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 realm
|
||||||
* @param user
|
* @param user
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import org.keycloak.models.RealmModel;
|
|||||||
import org.keycloak.models.UserModel;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@@ -99,10 +103,47 @@ public interface UserQueryProvider {
|
|||||||
*/
|
*/
|
||||||
List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults);
|
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);
|
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);
|
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);
|
List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,9 @@ import org.keycloak.models.RoleModel;
|
|||||||
import org.keycloak.models.UserModel;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -285,7 +285,9 @@ public class UserStorageManager implements UserProvider, OnUserCache {
|
|||||||
protected List<UserModel> importValidation(RealmModel realm, List<UserModel> users) {
|
protected List<UserModel> importValidation(RealmModel realm, List<UserModel> users) {
|
||||||
List<UserModel> tmp = new LinkedList<>();
|
List<UserModel> tmp = new LinkedList<>();
|
||||||
for (UserModel user : users) {
|
for (UserModel user : users) {
|
||||||
tmp.add(importValidation(realm, user));
|
UserModel model = importValidation(realm, user);
|
||||||
|
if (model == null) continue;
|
||||||
|
tmp.add(model);
|
||||||
}
|
}
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user