Merge pull request #347 from stianst/master
Ensure Realm names are unique
This commit is contained in:
commit
dabf101d96
11 changed files with 236 additions and 22 deletions
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ModelDuplicateException extends ModelException {
|
||||
|
||||
public ModelDuplicateException() {
|
||||
}
|
||||
|
||||
public ModelDuplicateException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ModelDuplicateException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ModelDuplicateException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ModelDuplicateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ModelException extends RuntimeException {
|
||||
|
||||
public ModelException() {
|
||||
}
|
||||
|
||||
public ModelException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ModelException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ModelException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ModelException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
|
|||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -18,7 +19,7 @@ public class JpaKeycloakSession implements KeycloakSession {
|
|||
protected EntityManager em;
|
||||
|
||||
public JpaKeycloakSession(EntityManager em) {
|
||||
this.em = em;
|
||||
this.em = PersistenceExceptionConverter.create(em);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package org.keycloak.models.jpa;
|
||||
|
||||
import org.hibernate.exception.ConstraintViolationException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class PersistenceExceptionConverter implements InvocationHandler {
|
||||
|
||||
private EntityManager em;
|
||||
|
||||
public static EntityManager create(EntityManager em) {
|
||||
return (EntityManager) Proxy.newProxyInstance(EntityManager.class.getClassLoader(), new Class[]{EntityManager.class}, new PersistenceExceptionConverter(em));
|
||||
}
|
||||
|
||||
private PersistenceExceptionConverter(EntityManager em) {
|
||||
this.em = em;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
try {
|
||||
return method.invoke(em, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable c = e.getCause();
|
||||
if (c.getCause() != null && c.getCause() instanceof ConstraintViolationException) {
|
||||
throw new ModelDuplicateException(c);
|
||||
} else {
|
||||
throw new ModelException(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.CollectionTable;
|
||||
import javax.persistence.Column;
|
||||
|
@ -37,7 +35,9 @@ public class RealmEntity {
|
|||
@Id
|
||||
protected String id;
|
||||
|
||||
@Column(unique = true)
|
||||
protected String name;
|
||||
|
||||
protected boolean enabled;
|
||||
protected boolean sslNotRequired;
|
||||
protected boolean registrationAllowed;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Target({TYPE})
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Inherited
|
||||
public @interface MongoIndex {
|
||||
|
||||
String[] fields();
|
||||
|
||||
String name() default "";
|
||||
|
||||
boolean unique() default false;
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package org.keycloak.models.mongo.api;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Target({TYPE})
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Inherited
|
||||
public @interface MongoIndexes {
|
||||
|
||||
MongoIndex[] value();
|
||||
|
||||
}
|
|
@ -6,11 +6,16 @@ import com.mongodb.DB;
|
|||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.MongoException;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.mongo.api.MongoCollection;
|
||||
import org.keycloak.models.mongo.api.MongoEntity;
|
||||
import org.keycloak.models.mongo.api.MongoField;
|
||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.MongoIndex;
|
||||
import org.keycloak.models.mongo.api.MongoIndexes;
|
||||
import org.keycloak.models.mongo.api.MongoStore;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.mongo.api.context.MongoTask;
|
||||
|
@ -88,6 +93,8 @@ public class MongoStoreImpl implements MongoStore {
|
|||
// dropDatabase();
|
||||
clearManagedCollections(managedEntityTypes);
|
||||
}
|
||||
|
||||
initManagedCollections(managedEntityTypes);
|
||||
}
|
||||
|
||||
protected void dropDatabase() {
|
||||
|
@ -106,6 +113,46 @@ public class MongoStoreImpl implements MongoStore {
|
|||
}
|
||||
}
|
||||
|
||||
protected void initManagedCollections(Class<? extends MongoEntity>[] managedEntityTypes) {
|
||||
for (Class<? extends MongoEntity> clazz : managedEntityTypes) {
|
||||
EntityInfo entityInfo = getEntityInfo(clazz);
|
||||
String dbCollectionName = entityInfo.getDbCollectionName();
|
||||
if (dbCollectionName != null && !database.collectionExists(dbCollectionName)) {
|
||||
DBCollection dbCollection = database.getCollection(dbCollectionName);
|
||||
|
||||
logger.debug("Created collection " + dbCollection.getName() + " in " + this.database.getName());
|
||||
|
||||
MongoIndex index = clazz.getAnnotation(MongoIndex.class);
|
||||
if (index != null) {
|
||||
createIndex(dbCollection, index);
|
||||
}
|
||||
|
||||
MongoIndexes indexes = clazz.getAnnotation(MongoIndexes.class);
|
||||
if (indexes != null) {
|
||||
for (MongoIndex i : indexes.value()) {
|
||||
createIndex(dbCollection, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void createIndex(DBCollection dbCollection, MongoIndex index) {
|
||||
BasicDBObject fields = new BasicDBObject();
|
||||
for (String f : index.fields()) {
|
||||
fields.put(f, 1);
|
||||
}
|
||||
String name = index.name();
|
||||
if (name.length() == 0) {
|
||||
name = null;
|
||||
}
|
||||
boolean unique = index.unique();
|
||||
|
||||
dbCollection.ensureIndex(fields, name, unique);
|
||||
|
||||
logger.debug("Created index " + fields + (unique ? " (unique)" : "") + " on " + dbCollection.getName() + " in " + this.database.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context) {
|
||||
Class<? extends MongoEntity> clazz = entity.getClass();
|
||||
|
@ -129,7 +176,15 @@ public class MongoStoreImpl implements MongoStore {
|
|||
// Adding "_id"
|
||||
dbObject.put("_id", currentId);
|
||||
|
||||
try {
|
||||
dbCollection.insert(dbObject);
|
||||
} catch (MongoException e) {
|
||||
if (e instanceof MongoException.DuplicateKey) {
|
||||
throw new ModelDuplicateException(e);
|
||||
} else {
|
||||
throw new ModelException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Treat object as created in this transaction (It is already submited to transaction)
|
||||
context.addCreatedEntity(entity);
|
||||
|
|
|
@ -1242,12 +1242,13 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
|
|||
|
||||
@Override
|
||||
public ApplicationModel getAdminApp() {
|
||||
return new ApplicationAdapter(this, realm.getAdminApp(), invocationContext);
|
||||
ApplicationEntity appData = getMongoStore().loadEntity(ApplicationEntity.class, realm.getAdminAppId(), invocationContext);
|
||||
return new ApplicationAdapter(this, appData, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdminApp(ApplicationModel app) {
|
||||
realm.setAdminApp(((ApplicationAdapter) app).getMongoEntity());
|
||||
realm.setAdminAppId(app.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,6 +7,8 @@ import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity;
|
|||
import org.keycloak.models.mongo.api.MongoCollection;
|
||||
import org.keycloak.models.mongo.api.MongoEntity;
|
||||
import org.keycloak.models.mongo.api.MongoField;
|
||||
import org.keycloak.models.mongo.api.MongoIndex;
|
||||
import org.keycloak.models.mongo.api.MongoIndexes;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -21,6 +23,7 @@ import java.util.Set;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "realms")
|
||||
@MongoIndex(fields = { "name" }, unique = true)
|
||||
public class RealmEntity extends AbstractMongoIdentifiableEntity implements MongoEntity {
|
||||
|
||||
private String name;
|
||||
|
@ -70,7 +73,7 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
|
|||
private long auditExpiration;
|
||||
private List<String> auditListeners = new ArrayList<String>();
|
||||
|
||||
private ApplicationEntity adminApp;
|
||||
private String adminAppId;
|
||||
|
||||
@MongoField
|
||||
public String getName() {
|
||||
|
@ -397,12 +400,12 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
|
|||
}
|
||||
|
||||
@MongoField
|
||||
public ApplicationEntity getAdminApp() {
|
||||
return adminApp;
|
||||
public String getAdminAppId() {
|
||||
return adminAppId;
|
||||
}
|
||||
|
||||
public void setAdminApp(ApplicationEntity adminApp) {
|
||||
this.adminApp = adminApp;
|
||||
public void setAdminAppId(String adminAppId) {
|
||||
this.adminAppId = adminAppId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.jboss.resteasy.util.GenericType;
|
|||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -49,6 +50,7 @@ public class RealmsAdminResource {
|
|||
}
|
||||
|
||||
public static final CacheControl noCache = new CacheControl();
|
||||
|
||||
static {
|
||||
noCache.setNoCache(true);
|
||||
}
|
||||
|
@ -97,16 +99,17 @@ public class RealmsAdminResource {
|
|||
|
||||
logger.debug("importRealm: {0}", rep.getRealm());
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
if (realmManager.getRealmByName(rep.getRealm()) != null) {
|
||||
return Flows.errors().exists("Realm " + rep.getRealm() + " already exists");
|
||||
}
|
||||
|
||||
try {
|
||||
RealmModel realm = realmManager.importRealm(rep);
|
||||
grantPermissionsToRealmCreator(realm);
|
||||
|
||||
URI location = realmUrl(uriInfo).build(realm.getName());
|
||||
logger.debug("imported realm success, sending back: {0}", location.toString());
|
||||
return Response.created(location).build();
|
||||
} catch (ModelDuplicateException e) {
|
||||
return Flows.errors().exists("Realm " + rep.getRealm() + " already exists");
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
|
@ -122,9 +125,16 @@ public class RealmsAdminResource {
|
|||
RealmManager realmManager = new RealmManager(session);
|
||||
for (InputPart inputPart : inputParts) {
|
||||
inputPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
|
||||
RealmRepresentation rep = inputPart.getBody(new GenericType<RealmRepresentation>(){});
|
||||
RealmRepresentation rep = inputPart.getBody(new GenericType<RealmRepresentation>() {
|
||||
});
|
||||
|
||||
RealmModel realm;
|
||||
try {
|
||||
realm = realmManager.importRealm(rep);
|
||||
} catch (ModelDuplicateException e) {
|
||||
return Flows.errors().exists("Realm " + rep.getRealm() + " already exists");
|
||||
}
|
||||
|
||||
RealmModel realm = realmManager.importRealm(rep);
|
||||
grantPermissionsToRealmCreator(realm);
|
||||
|
||||
if (inputParts.size() == 1) {
|
||||
|
|
Loading…
Reference in a new issue