keycloak-memoizeit
Changes
services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java 38(+38 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java 199(+199 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/MongoDBSessionFactory.java 35(+33 -2)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLSession.java 44(+31 -13)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/NoSQLTransaction.java 2(+1 -1)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RealmAdapter.java 439(+349 -90)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/RoleAdapter.java 9(+6 -3)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/UserAdapter.java 8(+6 -2)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java 155(+155 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java 11(+11 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java 85(+85 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java 77(+77 -0)
services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java 90(+90 -0)
Details
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java
index 9da8166..c3081b0 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/NoSQL.java
@@ -1,7 +1,8 @@
package org.keycloak.services.models.nosql.api;
import java.util.List;
-import java.util.Map;
+
+import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java
new file mode 100644
index 0000000..aa4eb48
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/api/query/NoSQLQueryBuilder.java
@@ -0,0 +1,38 @@
+package org.keycloak.services.models.nosql.api.query;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class NoSQLQueryBuilder {
+
+ private Map<String, Object> queryAttributes = new HashMap<String, Object>();
+
+ protected NoSQLQueryBuilder() {};
+
+ public static NoSQLQueryBuilder create(Class<? extends NoSQLQueryBuilder> builderClass) {
+ try {
+ return builderClass.newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public NoSQLQuery build() {
+ return new NoSQLQuery(queryAttributes);
+ }
+
+ public NoSQLQueryBuilder andCondition(String name, Object value) {
+ this.put(name, value);
+ return this;
+ }
+
+ public abstract NoSQLQueryBuilder inCondition(String name, Object[] values);
+
+ protected void put(String name, Object value) {
+ queryAttributes.put(name, value);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
index 7d61b85..fd6efa7 100644
--- a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBImpl.java
@@ -12,16 +12,13 @@ import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import org.bson.types.ObjectId;
-import org.jboss.resteasy.logging.Logger;
-import org.jboss.resteasy.spi.NotImplementedYetException;
-import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.nosql.api.AttributedNoSQLObject;
import org.keycloak.services.models.nosql.api.NoSQL;
import org.keycloak.services.models.nosql.api.NoSQLCollection;
import org.keycloak.services.models.nosql.api.NoSQLField;
import org.keycloak.services.models.nosql.api.NoSQLId;
import org.keycloak.services.models.nosql.api.NoSQLObject;
-import org.keycloak.services.models.nosql.api.NoSQLQuery;
+import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
import org.keycloak.services.models.nosql.api.types.Converter;
import org.keycloak.services.models.nosql.api.types.TypeConverter;
import org.keycloak.services.models.nosql.impl.types.BasicDBListToStringArrayConverter;
@@ -68,14 +65,14 @@ public class MongoDBImpl implements NoSQL {
dbObject.append(propName, propValue);
+ }
- // Adding attributes
- if (object instanceof AttributedNoSQLObject) {
- AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)object;
- Map<String, String> attributes = attributedObject.getAttributes();
- for (Map.Entry<String, String> attribute : attributes.entrySet()) {
- dbObject.append(attribute.getKey(), attribute.getValue());
- }
+ // Adding attributes
+ if (object instanceof AttributedNoSQLObject) {
+ AttributedNoSQLObject attributedObject = (AttributedNoSQLObject)object;
+ Map<String, String> attributes = attributedObject.getAttributes();
+ for (Map.Entry<String, String> attribute : attributes.entrySet()) {
+ dbObject.append(attribute.getKey(), attribute.getValue());
}
}
@@ -98,8 +95,7 @@ public class MongoDBImpl implements NoSQL {
@Override
public <T extends NoSQLObject> T loadObject(Class<T> type, String oid) {
- ObjectInfo<T> objectInfo = getObjectInfo(type);
- DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
+ DBCollection dbCollection = getDBCollectionForType(type);
BasicDBObject idQuery = new BasicDBObject("_id", new ObjectId(oid));
DBObject dbObject = dbCollection.findOne(idQuery);
@@ -122,15 +118,9 @@ public class MongoDBImpl implements NoSQL {
@Override
public <T extends NoSQLObject> List<T> loadObjects(Class<T> type, NoSQLQuery query) {
- Map<String, Object> queryAttributes = query.getQueryAttributes();
-
- ObjectInfo<T> objectInfo = getObjectInfo(type);
- DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
+ DBCollection dbCollection = getDBCollectionForType(type);
+ BasicDBObject dbQuery = getDBQueryFromQuery(query);
- BasicDBObject dbQuery = new BasicDBObject();
- for (Map.Entry<String, Object> queryAttr : queryAttributes.entrySet()) {
- dbQuery.append(queryAttr.getKey(), queryAttr.getValue());
- }
DBCursor cursor = dbCollection.find(dbQuery);
return convertCursor(type, cursor);
@@ -149,19 +139,21 @@ public class MongoDBImpl implements NoSQL {
@Override
public void removeObject(Class<? extends NoSQLObject> type, String oid) {
- ObjectInfo<?> objectInfo = getObjectInfo(type);
- DBCollection dbCollection = database.getCollection(objectInfo.getDbCollectionName());
+ DBCollection dbCollection = getDBCollectionForType(type);
- BasicDBObject query = new BasicDBObject("_id", new ObjectId(oid));
- dbCollection.remove(query);
+ BasicDBObject dbQuery = new BasicDBObject("_id", new ObjectId(oid));
+ dbCollection.remove(dbQuery);
}
@Override
public void removeObjects(Class<? extends NoSQLObject> type, NoSQLQuery query) {
- throw new NotImplementedYetException();
+ DBCollection dbCollection = getDBCollectionForType(type);
+ BasicDBObject dbQuery = getDBQueryFromQuery(query);
+
+ dbCollection.remove(dbQuery);
}
- // Possibility to add converters
+ // Possibility to add user-defined converters
public void addConverter(Converter<?, ?> converter) {
typeConverter.addConverter(converter);
}
@@ -171,6 +163,7 @@ public class MongoDBImpl implements NoSQL {
if (objectInfo == null) {
Property<String> idProperty = PropertyQueries.<String>createQuery(objectClass).addCriteria(new AnnotatedPropertyCriteria(NoSQLId.class)).getFirstResult();
if (idProperty == null) {
+ // TODO: should be allowed to have NoSQLObject classes without declared NoSQLId annotation?
throw new IllegalStateException("Class " + objectClass + " doesn't have property with declared annotation " + NoSQLId.class);
}
@@ -195,6 +188,10 @@ public class MongoDBImpl implements NoSQL {
private <T extends NoSQLObject> T convertObject(Class<T> type, DBObject dbObject) {
+ if (dbObject == null) {
+ return null;
+ }
+
ObjectInfo<T> objectInfo = getObjectInfo(type);
T object;
@@ -255,4 +252,18 @@ public class MongoDBImpl implements NoSQL {
return result;
}
+
+ private DBCollection getDBCollectionForType(Class<? extends NoSQLObject> type) {
+ ObjectInfo<?> objectInfo = getObjectInfo(type);
+ return database.getCollection(objectInfo.getDbCollectionName());
+ }
+
+ private BasicDBObject getDBQueryFromQuery(NoSQLQuery query) {
+ Map<String, Object> queryAttributes = query.getQueryAttributes();
+ BasicDBObject dbQuery = new BasicDBObject();
+ for (Map.Entry<String, Object> queryAttr : queryAttributes.entrySet()) {
+ dbQuery.append(queryAttr.getKey(), queryAttr.getValue());
+ }
+ return dbQuery;
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java
new file mode 100644
index 0000000..b3af732
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/MongoDBQueryBuilder.java
@@ -0,0 +1,33 @@
+package org.keycloak.services.models.nosql.impl;
+
+import com.mongodb.BasicDBObject;
+import org.bson.types.ObjectId;
+import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoDBQueryBuilder extends NoSQLQueryBuilder {
+
+ @Override
+ public NoSQLQueryBuilder inCondition(String name, Object[] values) {
+ if (values == null) {
+ values = new Object[0];
+ }
+
+ if ("_id".equals(name)) {
+ // we need to convert Strings to ObjectID
+ ObjectId[] objIds = new ObjectId[values.length];
+ for (int i=0 ; i<values.length ; i++) {
+ String id = values[i].toString();
+ ObjectId objectId = new ObjectId(id);
+ objIds[i] = objectId;
+ }
+ values = objIds;
+ }
+
+ BasicDBObject inObject = new BasicDBObject("$in", values);
+ put(name, inObject);
+ return this;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java b/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java
new file mode 100644
index 0000000..1398e27
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/impl/Utils.java
@@ -0,0 +1,56 @@
+package org.keycloak.services.models.nosql.impl;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class Utils {
+
+ private Utils() {};
+
+ /**
+ * Add item to the end of array
+ *
+ * @param inputArray could be null. In this case, method will return array of length 1 with added item
+ * @param item must be not-null
+ * @return array with added item to the end
+ */
+ public static <T> T[] addItemToArray(T[] inputArray, T item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item must be non-null");
+ }
+
+ T[] outputArray;
+ if (inputArray == null) {
+ outputArray = (T[])Array.newInstance(item.getClass(), 1);
+ } else {
+ outputArray = Arrays.copyOf(inputArray, inputArray.length + 1);
+ }
+ outputArray[outputArray.length - 1] = item;
+ return outputArray;
+ }
+
+ /**
+ * Return true if array contains specified item
+ * @param array could be null (In this case method always return false)
+ * @param item can't be null
+ * @return
+ */
+ public static boolean contains(Object[] array, Object item) {
+ if (item == null) {
+ throw new IllegalArgumentException("item must be non-null");
+ }
+
+ if (array != null) {
+ for (Object current : array) {
+ if (item.equals(current)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java
new file mode 100644
index 0000000..fc8a6e1
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/adapters/ApplicationAdapter.java
@@ -0,0 +1,199 @@
+package org.keycloak.services.models.nosql.keycloak.adapters;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.keycloak.services.models.ApplicationModel;
+import org.keycloak.services.models.RoleModel;
+import org.keycloak.services.models.UserModel;
+import org.keycloak.services.models.nosql.api.NoSQL;
+import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
+import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder;
+import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder;
+import org.keycloak.services.models.nosql.impl.Utils;
+import org.keycloak.services.models.nosql.keycloak.data.ApplicationData;
+import org.keycloak.services.models.nosql.keycloak.data.RoleData;
+import org.keycloak.services.models.nosql.keycloak.data.UserData;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ApplicationAdapter implements ApplicationModel {
+
+ private final ApplicationData application;
+ private final NoSQL noSQL;
+
+ private UserData resourceUser;
+
+ public ApplicationAdapter(ApplicationData applicationData, NoSQL noSQL) {
+ this.application = applicationData;
+ this.noSQL = noSQL;
+ }
+
+ @Override
+ public void updateResource() {
+ noSQL.saveObject(application);
+ }
+
+ @Override
+ public UserModel getResourceUser() {
+ // This is not thread-safe. Assumption is that ApplicationAdapter instance is per-client object
+ if (resourceUser == null) {
+ resourceUser = noSQL.loadObject(UserData.class, application.getResourceUserId());
+ }
+
+ return resourceUser != null ? new UserAdapter(resourceUser, noSQL) : null;
+ }
+
+ @Override
+ public String getId() {
+ return application.getId();
+ }
+
+ @Override
+ public String getName() {
+ return application.getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ application.setName(name);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return application.isEnabled();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ application.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean isSurrogateAuthRequired() {
+ return application.isSurrogateAuthRequired();
+ }
+
+ @Override
+ public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
+ application.setSurrogateAuthRequired(surrogateAuthRequired);
+ }
+
+ @Override
+ public String getManagementUrl() {
+ return application.getManagementUrl();
+ }
+
+ @Override
+ public void setManagementUrl(String url) {
+ application.setManagementUrl(url);
+ }
+
+ @Override
+ public RoleAdapter getRole(String name) {
+ NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class)
+ .andCondition("name", name)
+ .andCondition("applicationId", getId())
+ .build();
+ RoleData role = noSQL.loadSingleObject(RoleData.class, query);
+ if (role == null) {
+ return null;
+ } else {
+ return new RoleAdapter(role, noSQL);
+ }
+ }
+
+ @Override
+ public RoleAdapter addRole(String name) {
+ if (getRole(name) != null) {
+ throw new IllegalArgumentException("Role " + name + " already exists");
+ }
+
+ RoleData roleData = new RoleData();
+ roleData.setName(name);
+ roleData.setApplicationId(getId());
+
+ noSQL.saveObject(roleData);
+ return new RoleAdapter(roleData, noSQL);
+ }
+
+ @Override
+ public List<RoleModel> getRoles() {
+ NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class)
+ .andCondition("applicationId", getId())
+ .build();
+ List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
+
+ List<RoleModel> result = new ArrayList<RoleModel>();
+ for (RoleData role : roles) {
+ result.add(new RoleAdapter(role, noSQL));
+ }
+
+ return result;
+ }
+
+ @Override
+ public Set<String> getRoleMappings(UserModel user) {
+ UserData userData = ((UserAdapter)user).getUser();
+ String[] roleIds = userData.getRoleIds();
+
+ Set<String> result = new HashSet<String>();
+
+ NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class)
+ .inCondition("_id", roleIds)
+ .build();
+ List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
+ // TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically...
+ for (RoleData role : roles) {
+ if (getId().equals(role.getApplicationId())) {
+ result.add(role.getName());
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void addScope(UserModel agent, String roleName) {
+ RoleAdapter role = getRole(roleName);
+ if (role == null) {
+ throw new RuntimeException("Role not found");
+ }
+
+ addScope(agent, role);
+ }
+
+ @Override
+ public void addScope(UserModel agent, RoleModel role) {
+ UserData userData = ((UserAdapter)agent).getUser();
+ RoleData roleData = ((RoleAdapter)role).getRole();
+
+ String[] scopeIds = userData.getScopeIds();
+ scopeIds = Utils.addItemToArray(scopeIds, roleData.getId());
+ userData.setScopeIds(scopeIds);
+
+ noSQL.saveObject(userData);
+ }
+
+ @Override
+ public Set<String> getScope(UserModel agent) {
+ UserData userData = ((UserAdapter)agent).getUser();
+ String[] scopeIds = userData.getScopeIds();
+
+ Set<String> result = new HashSet<String>();
+
+ NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class)
+ .inCondition("_id", scopeIds)
+ .build();
+ List<RoleData> roles = noSQL.loadObjects(RoleData.class, query);
+ // TODO: Maybe improve to have roles and scopes in separate table? As actually we need to obtain all roles and then filter programmatically...
+ for (RoleData role : roles) {
+ if (getId().equals(role.getApplicationId())) {
+ result.add(role.getName());
+ }
+ }
+ return result;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java
new file mode 100644
index 0000000..32a84d7
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/PasswordCredentialHandler.java
@@ -0,0 +1,155 @@
+package org.keycloak.services.models.nosql.keycloak.credentials;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+
+import org.keycloak.services.models.nosql.api.NoSQL;
+import org.keycloak.services.models.nosql.api.query.NoSQLQuery;
+import org.keycloak.services.models.nosql.api.query.NoSQLQueryBuilder;
+import org.keycloak.services.models.nosql.impl.MongoDBQueryBuilder;
+import org.keycloak.services.models.nosql.keycloak.data.UserData;
+import org.keycloak.services.models.nosql.keycloak.data.credentials.PasswordData;
+import org.picketlink.idm.credential.Credentials;
+import org.picketlink.idm.credential.encoder.PasswordEncoder;
+import org.picketlink.idm.credential.encoder.SHAPasswordEncoder;
+
+/**
+ * Defacto forked from {@link org.picketlink.idm.credential.handler.PasswordCredentialHandler}
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PasswordCredentialHandler {
+
+ private static final String DEFAULT_SALT_ALGORITHM = "SHA1PRNG";
+
+ /**
+ * <p>
+ * Stores a <b>stateless</b> instance of {@link org.picketlink.idm.credential.encoder.PasswordEncoder} that should be used to encode passwords.
+ * </p>
+ */
+ public static final String PASSWORD_ENCODER = "PASSWORD_ENCODER";
+
+ private PasswordEncoder passwordEncoder = new SHAPasswordEncoder(512);;
+
+ public void setup(Map<String, Object> options) {
+ if (options != null) {
+ Object providedEncoder = options.get(PASSWORD_ENCODER);
+
+ if (providedEncoder != null) {
+ if (PasswordEncoder.class.isInstance(providedEncoder)) {
+ this.passwordEncoder = (PasswordEncoder) providedEncoder;
+ } else {
+ throw new IllegalArgumentException("The password encoder [" + providedEncoder
+ + "] must be an instance of " + PasswordEncoder.class.getName());
+ }
+ }
+ }
+ }
+
+ public Credentials.Status validate(NoSQL noSQL, UserData user, String passwordToValidate) {
+ Credentials.Status status = Credentials.Status.INVALID;
+
+ user = noSQL.loadObject(UserData.class, user.getId());
+
+ // If the user for the provided username cannot be found we fail validation
+ if (user != null) {
+ if (user.isEnabled()) {
+ NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class)
+ .andCondition("userId", user.getId())
+ .build();
+ PasswordData passwordData = noSQL.loadSingleObject(PasswordData.class, query);
+
+ // If the stored hash is null we automatically fail validation
+ if (passwordData != null) {
+ if (!isCredentialExpired(passwordData.getExpiryDate())) {
+
+ boolean matches = this.passwordEncoder.verify(saltPassword(passwordToValidate, passwordData.getSalt()), passwordData.getEncodedHash());
+
+ if (matches) {
+ status = Credentials.Status.VALID;
+ }
+ } else {
+ status = Credentials.Status.EXPIRED;
+ }
+ }
+ } else {
+ status = Credentials.Status.ACCOUNT_DISABLED;
+ }
+ }
+
+ return status;
+ }
+
+ public void update(NoSQL noSQL, UserData user, String password,
+ Date effectiveDate, Date expiryDate) {
+
+ // Try to look if user already has password
+ NoSQLQuery query = NoSQLQueryBuilder.create(MongoDBQueryBuilder.class)
+ .andCondition("userId", user.getId())
+ .build();
+
+ PasswordData passwordData = noSQL.loadSingleObject(PasswordData.class, query);
+ if (passwordData == null) {
+ passwordData = new PasswordData();
+ }
+
+ String passwordSalt = generateSalt();
+
+ passwordData.setSalt(passwordSalt);
+ passwordData.setEncodedHash(this.passwordEncoder.encode(saltPassword(password, passwordSalt)));
+
+ if (effectiveDate != null) {
+ passwordData.setEffectiveDate(effectiveDate);
+ }
+
+ passwordData.setExpiryDate(expiryDate);
+
+ passwordData.setUserId(user.getId());
+
+ noSQL.saveObject(passwordData);
+ }
+
+ /**
+ * <p>
+ * Salt the give <code>rawPassword</code> with the specified <code>salt</code> value.
+ * </p>
+ *
+ * @param rawPassword
+ * @param salt
+ * @return
+ */
+ private String saltPassword(String rawPassword, String salt) {
+ return salt + rawPassword;
+ }
+
+ /**
+ * <p>
+ * Generates a random string to be used as a salt for passwords.
+ * </p>
+ *
+ * @return
+ */
+ private String generateSalt() {
+ // TODO: always returns same salt (See https://issues.jboss.org/browse/PLINK-258)
+ /*SecureRandom pseudoRandom = null;
+
+ try {
+ pseudoRandom = SecureRandom.getInstance(DEFAULT_SALT_ALGORITHM);
+ pseudoRandom.setSeed(1024);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Error getting SecureRandom instance: " + DEFAULT_SALT_ALGORITHM, e);
+ }
+
+ return String.valueOf(pseudoRandom.nextLong());*/
+ return UUID.randomUUID().toString();
+ }
+
+ public static boolean isCredentialExpired(Date expiryDate) {
+ return expiryDate != null && new Date().compareTo(expiryDate) > 0;
+ }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java
new file mode 100644
index 0000000..a5dc8d2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/credentials/TOTPCredentialHandler.java
@@ -0,0 +1,11 @@
+package org.keycloak.services.models.nosql.keycloak.credentials;
+
+/**
+ * Defacto forked from {@link org.picketlink.idm.credential.handler.TOTPCredentialHandler}
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TOTPCredentialHandler extends PasswordCredentialHandler {
+
+ // TODO: implement
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java
new file mode 100644
index 0000000..7035f9a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/ApplicationData.java
@@ -0,0 +1,85 @@
+package org.keycloak.services.models.nosql.keycloak.data;
+
+import org.keycloak.services.models.nosql.api.NoSQLCollection;
+import org.keycloak.services.models.nosql.api.NoSQLField;
+import org.keycloak.services.models.nosql.api.NoSQLId;
+import org.keycloak.services.models.nosql.api.NoSQLObject;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "applications")
+public class ApplicationData implements NoSQLObject {
+
+ private String id;
+ private String name;
+ private boolean enabled;
+ private boolean surrogateAuthRequired;
+ private String managementUrl;
+
+ private String resourceUserId;
+ private String realmId;
+
+ @NoSQLId
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @NoSQLField
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @NoSQLField
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @NoSQLField
+ public boolean isSurrogateAuthRequired() {
+ return surrogateAuthRequired;
+ }
+
+ public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
+ this.surrogateAuthRequired = surrogateAuthRequired;
+ }
+
+ @NoSQLField
+ public String getManagementUrl() {
+ return managementUrl;
+ }
+
+ public void setManagementUrl(String managementUrl) {
+ this.managementUrl = managementUrl;
+ }
+
+ @NoSQLField
+ public String getResourceUserId() {
+ return resourceUserId;
+ }
+
+ public void setResourceUserId(String resourceUserId) {
+ this.resourceUserId = resourceUserId;
+ }
+
+ @NoSQLField
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java
new file mode 100644
index 0000000..a5ffad4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/credentials/PasswordData.java
@@ -0,0 +1,77 @@
+package org.keycloak.services.models.nosql.keycloak.data.credentials;
+
+import java.util.Date;
+
+import org.keycloak.services.models.nosql.api.NoSQLCollection;
+import org.keycloak.services.models.nosql.api.NoSQLField;
+import org.keycloak.services.models.nosql.api.NoSQLId;
+import org.keycloak.services.models.nosql.api.NoSQLObject;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "passwordCredentials")
+public class PasswordData implements NoSQLObject {
+
+ private String id;
+ private Date effectiveDate = new Date();
+ private Date expiryDate;
+ private String encodedHash;
+ private String salt;
+
+ private String userId;
+
+ @NoSQLId
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @NoSQLField
+ public Date getEffectiveDate() {
+ return effectiveDate;
+ }
+
+ public void setEffectiveDate(Date effectiveDate) {
+ this.effectiveDate = effectiveDate;
+ }
+
+ @NoSQLField
+ public Date getExpiryDate() {
+ return expiryDate;
+ }
+
+ public void setExpiryDate(Date expiryDate) {
+ this.expiryDate = expiryDate;
+ }
+
+ @NoSQLField
+ public String getEncodedHash() {
+ return encodedHash;
+ }
+
+ public void setEncodedHash(String encodedHash) {
+ this.encodedHash = encodedHash;
+ }
+
+ @NoSQLField
+ public String getSalt() {
+ return salt;
+ }
+
+ public void setSalt(String salt) {
+ this.salt = salt;
+ }
+
+ @NoSQLField
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java
new file mode 100644
index 0000000..d20b595
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/RequiredCredentialData.java
@@ -0,0 +1,90 @@
+package org.keycloak.services.models.nosql.keycloak.data;
+
+import org.keycloak.services.models.nosql.api.NoSQLCollection;
+import org.keycloak.services.models.nosql.api.NoSQLField;
+import org.keycloak.services.models.nosql.api.NoSQLId;
+import org.keycloak.services.models.nosql.api.NoSQLObject;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "requiredCredentials")
+public class RequiredCredentialData implements NoSQLObject {
+
+ public static final int CLIENT_TYPE_USER = 1;
+ public static final int CLIENT_TYPE_RESOURCE = 2;
+ public static final int CLIENT_TYPE_OAUTH_RESOURCE = 3;
+
+ private String id;
+
+ private String type;
+ private boolean input;
+ private boolean secret;
+ private String formLabel;
+
+ private String realmId;
+ private int clientType;
+
+ @NoSQLId
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @NoSQLField
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ @NoSQLField
+ public boolean isInput() {
+ return input;
+ }
+
+ public void setInput(boolean input) {
+ this.input = input;
+ }
+
+ @NoSQLField
+ public boolean isSecret() {
+ return secret;
+ }
+
+ public void setSecret(boolean secret) {
+ this.secret = secret;
+ }
+
+ @NoSQLField
+ public String getFormLabel() {
+ return formLabel;
+ }
+
+ public void setFormLabel(String formLabel) {
+ this.formLabel = formLabel;
+ }
+
+ @NoSQLField
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ @NoSQLField
+ public int getClientType() {
+ return clientType;
+ }
+
+ public void setClientType(int clientType) {
+ this.clientType = clientType;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java
new file mode 100644
index 0000000..0ed96a4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/models/nosql/keycloak/data/SocialLinkData.java
@@ -0,0 +1,54 @@
+package org.keycloak.services.models.nosql.keycloak.data;
+
+import org.keycloak.services.models.nosql.api.NoSQLCollection;
+import org.keycloak.services.models.nosql.api.NoSQLField;
+import org.keycloak.services.models.nosql.api.NoSQLId;
+import org.keycloak.services.models.nosql.api.NoSQLObject;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@NoSQLCollection(collectionName = "socialLinks")
+public class SocialLinkData implements NoSQLObject {
+
+ private String id;
+ private String socialUsername;
+ private String socialProvider;
+ private String userId;
+
+ @NoSQLId
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @NoSQLField
+ public String getSocialUsername() {
+ return socialUsername;
+ }
+
+ public void setSocialUsername(String socialUsername) {
+ this.socialUsername = socialUsername;
+ }
+
+ @NoSQLField
+ public String getSocialProvider() {
+ return socialProvider;
+ }
+
+ public void setSocialProvider(String socialProvider) {
+ this.socialProvider = socialProvider;
+ }
+
+ @NoSQLField
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 089ab39..dc8d711 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -8,8 +8,9 @@ import org.keycloak.models.picketlink.PicketlinkKeycloakSessionFactory;
import org.keycloak.models.picketlink.mappings.ApplicationEntity;
import org.keycloak.models.picketlink.mappings.RealmEntity;
import org.keycloak.services.models.KeycloakSessionFactory;
-import org.keycloak.services.models.nosql.adapters.MongoDBSessionFactory;
+import org.keycloak.services.models.nosql.keycloak.adapters.MongoDBSessionFactory;
import org.keycloak.services.models.picketlink.PicketlinkKeycloakSession;
+import org.keycloak.services.models.picketlink.PicketlinkKeycloakSessionFactory;
import org.keycloak.services.models.picketlink.mappings.ApplicationEntity;
import org.keycloak.services.models.picketlink.mappings.RealmEntity;
import org.keycloak.social.SocialRequestManager;
@@ -24,6 +25,8 @@ import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.servlet.ServletContext;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import java.util.HashSet;
@@ -61,7 +64,7 @@ public class KeycloakApplication extends Application {
public static KeycloakSessionFactory buildSessionFactory() {
// EntityManagerFactory emf = Persistence.createEntityManagerFactory("keycloak-identity-store");
// return new PicketlinkKeycloakSessionFactory(emf, buildPartitionManager());
- return new MongoDBSessionFactory("localhost", 27017, "keycloak");
+ return new MongoDBSessionFactory("localhost", 27017, "keycloak", true);
}
public KeycloakSessionFactory getFactory() {