Merge pull request #351 from stianst/constraints
Ensure application and client names are unique within realm
This commit is contained in:
commit
bb039576d4
33 changed files with 326 additions and 123 deletions
|
@ -22,7 +22,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a class="btn btn-primary" href="#/create/application/{{realm.realm}}">Add Application</a>
|
<a class="btn btn-primary" href="#/create/oauth-client/{{realm.realm}}">Add Client</a>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -26,12 +26,19 @@
|
||||||
<form class="form-horizontal" name="userForm" novalidate kc-read-only="!access.manageUsers">
|
<form class="form-horizontal" name="userForm" novalidate kc-read-only="!access.manageUsers">
|
||||||
<span class="fieldset-notice"><span class="required">*</span> Required fields</span>
|
<span class="fieldset-notice"><span class="required">*</span> Required fields</span>
|
||||||
<fieldset class="border-top">
|
<fieldset class="border-top">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label"for="id">ID</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input class="form-control" type="text" id="id" name="id" data-ng-model="user.id" autofocus data-ng-readonly="true">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-2 control-label"for="username">Username <span class="required" data-ng-show="create">*</span></label>
|
<label class="col-sm-2 control-label"for="username">Username <span class="required" data-ng-show="create">*</span></label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<!-- Characters >,<,/,\ are forbidden in username -->
|
<!-- Characters >,<,/,\ are forbidden in username -->
|
||||||
<input class="form-control" type="text" id="username" name="username" data-ng-model="user.username" autofocus
|
<input class="form-control" type="text" id="username" name="username" data-ng-model="user.username" autofocus
|
||||||
required ng-pattern="/^[^\<\>\\\/]*$/" data-ng-readonly="!create">
|
required ng-pattern="/^[^\<\>\\\/]*$/">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,8 @@ public interface UserModel {
|
||||||
|
|
||||||
String getLoginName();
|
String getLoginName();
|
||||||
|
|
||||||
|
void setLoginName(String loginName);
|
||||||
|
|
||||||
boolean isEnabled();
|
boolean isEnabled();
|
||||||
|
|
||||||
boolean isTotp();
|
boolean isTotp();
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.models.jpa;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -23,7 +24,11 @@ public class JpaKeycloakTransaction implements KeycloakTransaction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void commit() {
|
public void commit() {
|
||||||
|
try {
|
||||||
em.getTransaction().commit();
|
em.getTransaction().commit();
|
||||||
|
} catch (PersistenceException e) {
|
||||||
|
throw PersistenceExceptionConverter.convert(e.getCause() != null ? e.getCause() : e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -21,5 +21,6 @@ public class OAuthClientAdapter extends ClientAdapter implements OAuthClientMode
|
||||||
@Override
|
@Override
|
||||||
public void setClientId(String id) {
|
public void setClientId(String id) {
|
||||||
entity.setName(id);
|
entity.setName(id);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.ModelDuplicateException;
|
||||||
|
|
||||||
import javax.persistence.EntityExistsException;
|
import javax.persistence.EntityExistsException;
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.PersistenceException;
|
||||||
import java.lang.reflect.InvocationHandler;
|
import java.lang.reflect.InvocationHandler;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
@ -31,15 +32,18 @@ public class PersistenceExceptionConverter implements InvocationHandler {
|
||||||
try {
|
try {
|
||||||
return method.invoke(em, args);
|
return method.invoke(em, args);
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
Throwable c = e.getCause();
|
throw convert(e.getCause());
|
||||||
if (c.getCause() != null && c.getCause() instanceof ConstraintViolationException) {
|
|
||||||
throw new ModelDuplicateException(c);
|
|
||||||
} if (c instanceof EntityExistsException) {
|
|
||||||
throw new ModelDuplicateException(c);
|
|
||||||
} else {
|
|
||||||
throw new ModelException(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ModelException convert(Throwable t) {
|
||||||
|
if (t.getCause() != null && t.getCause() instanceof ConstraintViolationException) {
|
||||||
|
throw new ModelDuplicateException(t);
|
||||||
|
} if (t instanceof EntityExistsException) {
|
||||||
|
throw new ModelDuplicateException(t);
|
||||||
|
} else {
|
||||||
|
throw new ModelException(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -454,9 +454,6 @@ public class RealmAdapter implements RealmModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel addUser(String username) {
|
public UserModel addUser(String username) {
|
||||||
if (getUser(username) != null) {
|
|
||||||
throw new RuntimeException("Username already exists: " + username);
|
|
||||||
}
|
|
||||||
UserEntity entity = new UserEntity();
|
UserEntity entity = new UserEntity();
|
||||||
entity.setLoginName(username);
|
entity.setLoginName(username);
|
||||||
entity.setRealm(realm);
|
entity.setRealm(realm);
|
||||||
|
|
|
@ -34,6 +34,11 @@ public class UserAdapter implements UserModel {
|
||||||
return user.getLoginName();
|
return user.getLoginName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLoginName(String loginName) {
|
||||||
|
user.setLoginName(loginName);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return user.isEnabled();
|
return user.isEnabled();
|
||||||
|
|
|
@ -7,10 +7,13 @@ import javax.persistence.Entity;
|
||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
import javax.persistence.GeneratedValue;
|
import javax.persistence.GeneratedValue;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.JoinTable;
|
import javax.persistence.JoinTable;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
import javax.persistence.OneToOne;
|
import javax.persistence.OneToOne;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -30,9 +33,6 @@ public class ApplicationEntity extends ClientEntity {
|
||||||
private String managementUrl;
|
private String managementUrl;
|
||||||
private boolean bearerOnly;
|
private boolean bearerOnly;
|
||||||
|
|
||||||
@ManyToOne()
|
|
||||||
private RealmEntity realm;
|
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "application")
|
@OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "application")
|
||||||
Collection<ApplicationRoleEntity> roles = new ArrayList<ApplicationRoleEntity>();
|
Collection<ApplicationRoleEntity> roles = new ArrayList<ApplicationRoleEntity>();
|
||||||
|
|
||||||
|
@ -80,14 +80,6 @@ public class ApplicationEntity extends ClientEntity {
|
||||||
this.defaultRoles = defaultRoles;
|
this.defaultRoles = defaultRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RealmEntity getRealm() {
|
|
||||||
return realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealm(RealmEntity realm) {
|
|
||||||
this.realm = realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBearerOnly() {
|
public boolean isBearerOnly() {
|
||||||
return bearerOnly;
|
return bearerOnly;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
import javax.persistence.CascadeType;
|
import javax.persistence.CascadeType;
|
||||||
import javax.persistence.CollectionTable;
|
import javax.persistence.CollectionTable;
|
||||||
|
import javax.persistence.Column;
|
||||||
import javax.persistence.ElementCollection;
|
import javax.persistence.ElementCollection;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.FetchType;
|
import javax.persistence.FetchType;
|
||||||
|
@ -11,8 +12,11 @@ import javax.persistence.GeneratedValue;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.Inheritance;
|
import javax.persistence.Inheritance;
|
||||||
import javax.persistence.InheritanceType;
|
import javax.persistence.InheritanceType;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -21,12 +25,14 @@ import java.util.Set;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Inheritance(strategy = InheritanceType.JOINED)
|
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
|
||||||
public class ClientEntity {
|
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"realm", "name"})})
|
||||||
|
public abstract class ClientEntity {
|
||||||
@Id
|
@Id
|
||||||
@GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
|
@GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
|
||||||
@GeneratedValue(generator = "keycloak_generator")
|
@GeneratedValue(generator = "keycloak_generator")
|
||||||
private String id;
|
private String id;
|
||||||
|
@Column(name = "name")
|
||||||
private String name;
|
private String name;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private String secret;
|
private String secret;
|
||||||
|
@ -34,6 +40,9 @@ public class ClientEntity {
|
||||||
private int notBefore;
|
private int notBefore;
|
||||||
private boolean publicClient;
|
private boolean publicClient;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "realm")
|
||||||
|
protected RealmEntity realm;
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
@CollectionTable
|
@CollectionTable
|
||||||
|
@ -42,6 +51,13 @@ public class ClientEntity {
|
||||||
@CollectionTable
|
@CollectionTable
|
||||||
protected Set<String> redirectUris = new HashSet<String>();
|
protected Set<String> redirectUris = new HashSet<String>();
|
||||||
|
|
||||||
|
public RealmEntity getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealm(RealmEntity realm) {
|
||||||
|
this.realm = realm;
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
|
|
|
@ -1,20 +1,8 @@
|
||||||
package org.keycloak.models.jpa.entities;
|
package org.keycloak.models.jpa.entities;
|
||||||
|
|
||||||
import javax.persistence.CollectionTable;
|
|
||||||
import javax.persistence.ElementCollection;
|
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.FetchType;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.ManyToOne;
|
|
||||||
import javax.persistence.NamedQueries;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.OneToOne;
|
|
||||||
|
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -27,17 +15,4 @@ import java.util.Set;
|
||||||
})
|
})
|
||||||
@Entity
|
@Entity
|
||||||
public class OAuthClientEntity extends ClientEntity {
|
public class OAuthClientEntity extends ClientEntity {
|
||||||
|
|
||||||
@ManyToOne()
|
|
||||||
private RealmEntity realm;
|
|
||||||
|
|
||||||
public RealmEntity getRealm() {
|
|
||||||
return realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRealm(RealmEntity realm) {
|
|
||||||
this.realm = realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class RealmEntity {
|
||||||
@JoinTable(name="AuthProviders")
|
@JoinTable(name="AuthProviders")
|
||||||
List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
|
List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
||||||
Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
|
Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||||
|
|
|
@ -10,12 +10,15 @@ import javax.persistence.ElementCollection;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.GeneratedValue;
|
import javax.persistence.GeneratedValue;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.MapKeyColumn;
|
import javax.persistence.MapKeyColumn;
|
||||||
import javax.persistence.NamedQueries;
|
import javax.persistence.NamedQueries;
|
||||||
import javax.persistence.NamedQuery;
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.OneToMany;
|
import javax.persistence.OneToMany;
|
||||||
import javax.persistence.OneToOne;
|
import javax.persistence.OneToOne;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -35,6 +38,10 @@ import java.util.Set;
|
||||||
@NamedQuery(name="getRealmUserByFirstLastName", query="select u from UserEntity u where u.firstName = :first and u.lastName = :last and u.realm = :realm")
|
@NamedQuery(name="getRealmUserByFirstLastName", query="select u from UserEntity u where u.firstName = :first and u.lastName = :last and u.realm = :realm")
|
||||||
})
|
})
|
||||||
@Entity
|
@Entity
|
||||||
|
@Table(uniqueConstraints = {
|
||||||
|
@UniqueConstraint(columnNames = { "realm", "loginName" }),
|
||||||
|
@UniqueConstraint(columnNames = { "realm", "email" })
|
||||||
|
})
|
||||||
public class UserEntity {
|
public class UserEntity {
|
||||||
@Id
|
@Id
|
||||||
@GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
|
@GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
|
||||||
|
@ -52,6 +59,7 @@ public class UserEntity {
|
||||||
|
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "realm")
|
||||||
protected RealmEntity realm;
|
protected RealmEntity realm;
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
|
|
|
@ -23,4 +23,6 @@ public @interface MongoIndex {
|
||||||
|
|
||||||
boolean unique() default false;
|
boolean unique() default false;
|
||||||
|
|
||||||
|
boolean sparse() default false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,10 +147,22 @@ public class MongoStoreImpl implements MongoStore {
|
||||||
name = null;
|
name = null;
|
||||||
}
|
}
|
||||||
boolean unique = index.unique();
|
boolean unique = index.unique();
|
||||||
|
boolean sparse = index.sparse();
|
||||||
|
|
||||||
dbCollection.ensureIndex(fields, name, unique);
|
BasicDBObject options = new BasicDBObject();
|
||||||
|
if (name != null) {
|
||||||
|
options.put("name", name);
|
||||||
|
}
|
||||||
|
if (unique) {
|
||||||
|
options.put("unique", unique);
|
||||||
|
}
|
||||||
|
if (sparse) {
|
||||||
|
options.put("sparse", sparse);
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug("Created index " + fields + (unique ? " (unique)" : "") + " on " + dbCollection.getName() + " in " + this.database.getName());
|
dbCollection.ensureIndex(fields, options);
|
||||||
|
|
||||||
|
logger.debug("Created index " + fields + "(options: " + options + ") on " + dbCollection.getName() + " in " + this.database.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -39,9 +39,11 @@ public class MongoEntityMapper<T extends MongoEntity> implements Mapper<T, Basic
|
||||||
String propName = property.getName();
|
String propName = property.getName();
|
||||||
Object propValue = property.getValue(applicationObject);
|
Object propValue = property.getValue(applicationObject);
|
||||||
|
|
||||||
Object dbValue = propValue == null ? null : mapperRegistry.convertApplicationObjectToDBObject(propValue, Object.class);
|
if (propValue != null) {
|
||||||
|
Object dbValue = mapperRegistry.convertApplicationObjectToDBObject(propValue, Object.class);
|
||||||
dbObject.put(propName, dbValue);
|
dbObject.put(propName, dbValue);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return dbObject;
|
return dbObject;
|
||||||
}
|
}
|
||||||
|
|
|
@ -490,10 +490,6 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
|
||||||
|
|
||||||
// Add just user entity without defaultRoles
|
// Add just user entity without defaultRoles
|
||||||
protected UserAdapter addUserEntity(String username) {
|
protected UserAdapter addUserEntity(String username) {
|
||||||
if (getUser(username) != null) {
|
|
||||||
throw new IllegalArgumentException("User " + username + " already exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
UserEntity userEntity = new UserEntity();
|
UserEntity userEntity = new UserEntity();
|
||||||
userEntity.setLoginName(username);
|
userEntity.setLoginName(username);
|
||||||
// Compatibility with JPA model, which has user disabled by default
|
// Compatibility with JPA model, which has user disabled by default
|
||||||
|
|
|
@ -34,6 +34,12 @@ public class UserAdapter extends AbstractMongoAdapter<UserEntity> implements Use
|
||||||
return user.getLoginName();
|
return user.getLoginName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLoginName(String loginName) {
|
||||||
|
user.setLoginName(loginName);
|
||||||
|
updateUser();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return user.isEnabled();
|
return user.isEnabled();
|
||||||
|
|
|
@ -7,12 +7,14 @@ import com.mongodb.DBObject;
|
||||||
import com.mongodb.QueryBuilder;
|
import com.mongodb.QueryBuilder;
|
||||||
import org.keycloak.models.mongo.api.MongoCollection;
|
import org.keycloak.models.mongo.api.MongoCollection;
|
||||||
import org.keycloak.models.mongo.api.MongoField;
|
import org.keycloak.models.mongo.api.MongoField;
|
||||||
|
import org.keycloak.models.mongo.api.MongoIndex;
|
||||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
@MongoCollection(collectionName = "applications")
|
@MongoCollection(collectionName = "applications")
|
||||||
|
@MongoIndex(name = "name-within-realm", fields = { "realmId", "name" }, unique = true)
|
||||||
public class ApplicationEntity extends ClientEntity {
|
public class ApplicationEntity extends ClientEntity {
|
||||||
|
|
||||||
private boolean surrogateAuthRequired;
|
private boolean surrogateAuthRequired;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.keycloak.models.mongo.keycloak.entities;
|
package org.keycloak.models.mongo.keycloak.entities;
|
||||||
|
|
||||||
import org.keycloak.models.mongo.api.MongoCollection;
|
import org.keycloak.models.mongo.api.MongoCollection;
|
||||||
|
import org.keycloak.models.mongo.api.MongoIndex;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ import java.util.List;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
@MongoCollection(collectionName = "oauthClients")
|
@MongoCollection(collectionName = "oauthClients")
|
||||||
|
@MongoIndex(name = "name-within-realm", fields = { "realmId", "name" }, unique = true)
|
||||||
public class OAuthClientEntity extends ClientEntity {
|
public class OAuthClientEntity extends ClientEntity {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import org.keycloak.models.mongo.api.AbstractMongoIdentifiableEntity;
|
||||||
import org.keycloak.models.mongo.api.MongoCollection;
|
import org.keycloak.models.mongo.api.MongoCollection;
|
||||||
import org.keycloak.models.mongo.api.MongoEntity;
|
import org.keycloak.models.mongo.api.MongoEntity;
|
||||||
import org.keycloak.models.mongo.api.MongoField;
|
import org.keycloak.models.mongo.api.MongoField;
|
||||||
|
import org.keycloak.models.mongo.api.MongoIndex;
|
||||||
|
import org.keycloak.models.mongo.api.MongoIndexes;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -14,6 +16,10 @@ import java.util.Map;
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
@MongoCollection(collectionName = "users")
|
@MongoCollection(collectionName = "users")
|
||||||
|
@MongoIndexes({
|
||||||
|
@MongoIndex(name = "loginName-within-realm", fields = { "realmId", "loginName" }, unique = true),
|
||||||
|
@MongoIndex(name = "email-within-realm", fields = { "emailRealm" }, unique = true, sparse = true),
|
||||||
|
})
|
||||||
public class UserEntity extends AbstractMongoIdentifiableEntity implements MongoEntity {
|
public class UserEntity extends AbstractMongoIdentifiableEntity implements MongoEntity {
|
||||||
|
|
||||||
private String loginName;
|
private String loginName;
|
||||||
|
@ -76,6 +82,15 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
|
||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MongoField
|
||||||
|
// TODO This is required as Mongo doesn't support sparse indexes with compound keys (see https://jira.mongodb.org/browse/SERVER-2193)
|
||||||
|
public String getEmailRealm() {
|
||||||
|
return email != null ? realmId + "//" + email : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailRealm(String emailRealm) {
|
||||||
|
}
|
||||||
|
|
||||||
@MongoField
|
@MongoField
|
||||||
public boolean isEmailVerified() {
|
public boolean isEmailVerified() {
|
||||||
return emailVerified;
|
return emailVerified;
|
||||||
|
|
|
@ -99,6 +99,10 @@ public class AbstractModelTest {
|
||||||
} else {
|
} else {
|
||||||
identitySession.getTransaction().commit();
|
identitySession.getTransaction().commit();
|
||||||
}
|
}
|
||||||
|
resetSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resetSession() {
|
||||||
identitySession.close();
|
identitySession.close();
|
||||||
identitySession = factory.createSession();
|
identitySession = factory.createSession();
|
||||||
identitySession.getTransaction().begin();
|
identitySession.getTransaction().begin();
|
||||||
|
|
|
@ -218,9 +218,9 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
|
|
||||||
realmModel.addScopeMapping(app, realmRole);
|
realmModel.addScopeMapping(app, realmRole);
|
||||||
|
|
||||||
Assert.assertTrue(identitySession.removeRealm(realmModel.getId()));
|
Assert.assertTrue(realmManager.removeRealm(realmModel));
|
||||||
Assert.assertFalse(identitySession.removeRealm(realmModel.getId()));
|
Assert.assertFalse(realmManager.removeRealm(realmModel));
|
||||||
Assert.assertNull(identitySession.getRealm(realmModel.getId()));
|
Assert.assertNull(realmManager.getRealm(realmModel.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -267,7 +267,7 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
UserModel user3 = realmModel.addUser("doublelast");
|
UserModel user3 = realmModel.addUser("doublelast");
|
||||||
user3.setFirstName("Ole");
|
user3.setFirstName("Ole");
|
||||||
user3.setLastName("Alver Veland");
|
user3.setLastName("Alver Veland");
|
||||||
user3.setEmail("knut@redhat.com");
|
user3.setEmail("knut2@redhat.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
RealmManager adapter = realmManager;
|
RealmManager adapter = realmManager;
|
||||||
|
@ -522,17 +522,131 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
commit(true);
|
commit(true);
|
||||||
|
|
||||||
// Ty to rename realm to duplicate name
|
// Ty to rename realm to duplicate name
|
||||||
realmModel = realmManager.createRealm("JUGGLER2");
|
realmManager.createRealm("JUGGLER2");
|
||||||
|
commit();
|
||||||
|
try {
|
||||||
|
realmManager.getRealmByName("JUGGLER2").setName("JUGGLER");
|
||||||
|
commit();
|
||||||
|
Assert.fail("Expected exception");
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAppNameCollisions() throws Exception {
|
||||||
|
realmManager.createRealm("JUGGLER1").addApplication("app1");
|
||||||
|
realmManager.createRealm("JUGGLER2").addApplication("app1");
|
||||||
|
|
||||||
commit();
|
commit();
|
||||||
|
|
||||||
realmModel = realmManager.getRealmByName("JUGGLER2");
|
// Try to create app with duplicate name
|
||||||
try {
|
try {
|
||||||
realmModel.setName("JUGGLER");
|
realmManager.getRealmByName("JUGGLER1").addApplication("app1");
|
||||||
commit();
|
commit();
|
||||||
Assert.fail("Expected exception");
|
Assert.fail("Expected exception");
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
}
|
}
|
||||||
commit(true);
|
commit(true);
|
||||||
|
|
||||||
|
// Ty to rename app to duplicate name
|
||||||
|
realmManager.getRealmByName("JUGGLER1").addApplication("app2");
|
||||||
|
commit();
|
||||||
|
try {
|
||||||
|
realmManager.getRealmByName("JUGGLER1").getApplicationByName("app2").setName("app1");
|
||||||
|
commit();
|
||||||
|
Assert.fail("Expected exception");
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientNameCollisions() throws Exception {
|
||||||
|
realmManager.createRealm("JUGGLER1").addOAuthClient("client1");
|
||||||
|
realmManager.createRealm("JUGGLER2").addOAuthClient("client1");
|
||||||
|
|
||||||
|
commit();
|
||||||
|
|
||||||
|
// Try to create app with duplicate name
|
||||||
|
try {
|
||||||
|
realmManager.getRealmByName("JUGGLER1").addOAuthClient("client1");
|
||||||
|
commit();
|
||||||
|
Assert.fail("Expected exception");
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
}
|
||||||
|
commit(true);
|
||||||
|
|
||||||
|
// Ty to rename app to duplicate name
|
||||||
|
realmManager.getRealmByName("JUGGLER1").addOAuthClient("client2");
|
||||||
|
commit();
|
||||||
|
try {
|
||||||
|
realmManager.getRealmByName("JUGGLER1").getOAuthClient("client2").setClientId("client1");
|
||||||
|
commit();
|
||||||
|
Assert.fail("Expected exception");
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUsernameCollisions() throws Exception {
|
||||||
|
realmManager.createRealm("JUGGLER1").addUser("user1");
|
||||||
|
realmManager.createRealm("JUGGLER2").addUser("user1");
|
||||||
|
commit();
|
||||||
|
|
||||||
|
// Try to create user with duplicate login name
|
||||||
|
try {
|
||||||
|
realmManager.getRealmByName("JUGGLER1").addUser("user1");
|
||||||
|
commit();
|
||||||
|
Assert.fail("Expected exception");
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
}
|
||||||
|
commit(true);
|
||||||
|
|
||||||
|
// Ty to rename user to duplicate login name
|
||||||
|
realmManager.getRealmByName("JUGGLER1").addUser("user2");
|
||||||
|
commit();
|
||||||
|
try {
|
||||||
|
realmManager.getRealmByName("JUGGLER1").getUser("user2").setLoginName("user1");
|
||||||
|
commit();
|
||||||
|
Assert.fail("Expected exception");
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmailCollisions() throws Exception {
|
||||||
|
realmManager.createRealm("JUGGLER1").addUser("user1").setEmail("email@example.com");
|
||||||
|
realmManager.createRealm("JUGGLER2").addUser("user1").setEmail("email@example.com");
|
||||||
|
commit();
|
||||||
|
|
||||||
|
// Try to create user with duplicate email
|
||||||
|
try {
|
||||||
|
realmManager.getRealmByName("JUGGLER1").addUser("user2").setEmail("email@example.com");
|
||||||
|
commit();
|
||||||
|
Assert.fail("Expected exception");
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSession();
|
||||||
|
|
||||||
|
// Ty to rename user to duplicate email
|
||||||
|
realmManager.getRealmByName("JUGGLER1").addUser("user3").setEmail("email2@example.com");
|
||||||
|
commit();
|
||||||
|
try {
|
||||||
|
realmManager.getRealmByName("JUGGLER1").getUser("user3").setEmail("email@example.com");
|
||||||
|
commit();
|
||||||
|
Assert.fail("Expected exception");
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
{
|
{
|
||||||
"username" : "REALM_COMPOSITE_1_USER",
|
"username" : "REALM_COMPOSITE_1_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user1@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
{
|
{
|
||||||
"username" : "REALM_ROLE_1_USER",
|
"username" : "REALM_ROLE_1_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user2@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
{
|
{
|
||||||
"username" : "REALM_APP_COMPOSITE_USER",
|
"username" : "REALM_APP_COMPOSITE_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user3@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
{
|
{
|
||||||
"username" : "REALM_APP_ROLE_USER",
|
"username" : "REALM_APP_ROLE_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user4@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
{
|
{
|
||||||
"username" : "APP_COMPOSITE_USER",
|
"username" : "APP_COMPOSITE_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user5@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
|
|
@ -104,9 +104,9 @@ public class RealmManager {
|
||||||
|
|
||||||
public boolean removeRealm(RealmModel realm) {
|
public boolean removeRealm(RealmModel realm) {
|
||||||
boolean removed = identitySession.removeRealm(realm.getId());
|
boolean removed = identitySession.removeRealm(realm.getId());
|
||||||
|
if (removed) {
|
||||||
getKeycloakAdminstrationRealm().removeApplication(realm.getAdminApp().getId());
|
getKeycloakAdminstrationRealm().removeApplication(realm.getAdminApp().getId());
|
||||||
|
}
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.jboss.resteasy.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -17,6 +18,7 @@ import org.keycloak.services.managers.ModelToRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.ResourceAdminManager;
|
import org.keycloak.services.managers.ResourceAdminManager;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
|
@ -32,6 +34,7 @@ import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Application;
|
import javax.ws.rs.core.Application;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -74,11 +77,16 @@ public class ApplicationResource {
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public void update(final ApplicationRepresentation rep) {
|
public Response update(final ApplicationRepresentation rep) {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
ApplicationManager applicationManager = new ApplicationManager(new RealmManager(session));
|
ApplicationManager applicationManager = new ApplicationManager(new RealmManager(session));
|
||||||
|
try {
|
||||||
applicationManager.updateApplication(rep, application);
|
applicationManager.updateApplication(rep, application);
|
||||||
|
return Response.noContent().build();
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
return Flows.errors().exists("Application " + rep.getName() + " already exists");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||||
import org.keycloak.services.managers.ApplicationManager;
|
import org.keycloak.services.managers.ApplicationManager;
|
||||||
|
@ -72,12 +73,13 @@ public class ApplicationsResource {
|
||||||
public Response createApplication(final @Context UriInfo uriInfo, final ApplicationRepresentation rep) {
|
public Response createApplication(final @Context UriInfo uriInfo, final ApplicationRepresentation rep) {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
if (realm.getApplicationNameMap().containsKey(rep.getName())) {
|
|
||||||
return Flows.errors().exists("Application " + rep.getName() + " already exists");
|
|
||||||
}
|
|
||||||
ApplicationManager resourceManager = new ApplicationManager(new RealmManager(session));
|
ApplicationManager resourceManager = new ApplicationManager(new RealmManager(session));
|
||||||
|
try {
|
||||||
ApplicationModel applicationModel = resourceManager.createApplication(realm, rep);
|
ApplicationModel applicationModel = resourceManager.createApplication(realm, rep);
|
||||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(applicationModel.getName()).build()).build();
|
return Response.created(uriInfo.getAbsolutePathBuilder().path(applicationModel.getName()).build()).build();
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
return Flows.errors().exists("Application " + rep.getName() + " already exists");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{app-name}")
|
@Path("{app-name}")
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.logging.Logger;
|
import org.jboss.resteasy.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.OAuthClientModel;
|
import org.keycloak.models.OAuthClientModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
@ -12,6 +13,7 @@ import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||||
import org.keycloak.services.managers.ModelToRepresentation;
|
import org.keycloak.services.managers.ModelToRepresentation;
|
||||||
import org.keycloak.services.managers.OAuthClientManager;
|
import org.keycloak.services.managers.OAuthClientManager;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
|
@ -24,6 +26,7 @@ import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.core.Application;
|
import javax.ws.rs.core.Application;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -64,11 +67,16 @@ public class OAuthClientResource {
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public void update(final OAuthClientRepresentation rep) {
|
public Response update(final OAuthClientRepresentation rep) {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
OAuthClientManager manager = new OAuthClientManager(realm);
|
OAuthClientManager manager = new OAuthClientManager(realm);
|
||||||
|
try {
|
||||||
manager.update(rep, oauthClient);
|
manager.update(rep, oauthClient);
|
||||||
|
return Response.noContent().build();
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
return Flows.errors().exists("Client " + rep.getName() + " already exists");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,12 @@ import org.jboss.resteasy.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.OAuthClientModel;
|
import org.keycloak.models.OAuthClientModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||||
import org.keycloak.services.managers.OAuthClientManager;
|
import org.keycloak.services.managers.OAuthClientManager;
|
||||||
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
@ -74,8 +76,12 @@ public class OAuthClientsResource {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
OAuthClientManager resourceManager = new OAuthClientManager(realm);
|
OAuthClientManager resourceManager = new OAuthClientManager(realm);
|
||||||
|
try {
|
||||||
OAuthClientModel oauth = resourceManager.create(rep);
|
OAuthClientModel oauth = resourceManager.create(rep);
|
||||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(oauth.getId()).build()).build();
|
return Response.created(uriInfo.getAbsolutePathBuilder().path(oauth.getId()).build()).build();
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
return Flows.errors().exists("Client " + rep.getName() + " already exists");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{id}")
|
@Path("{id}")
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.keycloak.audit.Event;
|
||||||
import org.keycloak.audit.EventQuery;
|
import org.keycloak.audit.EventQuery;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.representations.adapters.action.SessionStats;
|
import org.keycloak.representations.adapters.action.SessionStats;
|
||||||
import org.keycloak.representations.idm.RealmAuditRepresentation;
|
import org.keycloak.representations.idm.RealmAuditRepresentation;
|
||||||
|
@ -18,10 +19,12 @@ import org.keycloak.services.managers.ModelToRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.ResourceAdminManager;
|
import org.keycloak.services.managers.ResourceAdminManager;
|
||||||
import org.keycloak.services.managers.TokenManager;
|
import org.keycloak.services.managers.TokenManager;
|
||||||
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -94,11 +97,16 @@ public class RealmAdminResource {
|
||||||
|
|
||||||
@PUT
|
@PUT
|
||||||
@Consumes("application/json")
|
@Consumes("application/json")
|
||||||
public void updateRealm(final RealmRepresentation rep) {
|
public Response updateRealm(final RealmRepresentation rep) {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
logger.debug("updating realm: " + realm.getName());
|
logger.debug("updating realm: " + realm.getName());
|
||||||
|
try {
|
||||||
new RealmManager(session).updateRealm(rep, realm);
|
new RealmManager(session).updateRealm(rep, realm);
|
||||||
|
return Response.noContent().build();
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
return Flows.errors().exists("Realm " + rep.getRealm() + " already exists");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
|
|
|
@ -8,6 +8,7 @@ import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
@ -84,14 +85,20 @@ public class UsersResource {
|
||||||
@Path("{username}")
|
@Path("{username}")
|
||||||
@PUT
|
@PUT
|
||||||
@Consumes("application/json")
|
@Consumes("application/json")
|
||||||
public void updateUser(final @PathParam("username") String username, final UserRepresentation rep) {
|
public Response updateUser(final @PathParam("username") String username, final UserRepresentation rep) {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
|
try {
|
||||||
UserModel user = realm.getUser(username);
|
UserModel user = realm.getUser(username);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new NotFoundException("User not found");
|
throw new NotFoundException("User not found");
|
||||||
}
|
}
|
||||||
updateUserFromRep(user, rep);
|
updateUserFromRep(user, rep);
|
||||||
|
|
||||||
|
return Response.noContent().build();
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
return Flows.errors().exists("User exists with same username or email");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
|
@ -99,17 +106,14 @@ public class UsersResource {
|
||||||
public Response createUser(final @Context UriInfo uriInfo, final UserRepresentation rep) {
|
public Response createUser(final @Context UriInfo uriInfo, final UserRepresentation rep) {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
if (realm.getUser(rep.getUsername()) != null) {
|
try {
|
||||||
return Flows.errors().exists("User with username " + rep.getUsername() + " already exists");
|
|
||||||
}
|
|
||||||
UserModel user = realm.addUser(rep.getUsername());
|
UserModel user = realm.addUser(rep.getUsername());
|
||||||
if (user == null) {
|
|
||||||
throw new NotFoundException("User not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUserFromRep(user, rep);
|
updateUserFromRep(user, rep);
|
||||||
|
|
||||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getLoginName()).build()).build();
|
return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getLoginName()).build()).build();
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
return Flows.errors().exists("User exists with same username or email");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUserFromRep(UserModel user, UserRepresentation rep) {
|
private void updateUserFromRep(UserModel user, UserRepresentation rep) {
|
||||||
|
|
|
@ -75,12 +75,12 @@ public class RegisterTest {
|
||||||
loginPage.clickRegister();
|
loginPage.clickRegister();
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "email", "test-user@localhost", "password", "password");
|
registerPage.register("firstName", "lastName", "registerExistingUseremail", "test-user@localhost", "password", "password");
|
||||||
|
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
Assert.assertEquals("Username already exists", registerPage.getError());
|
Assert.assertEquals("Username already exists", registerPage.getError());
|
||||||
|
|
||||||
events.expectRegister("test-user@localhost", "email").user((String) null).error("username_in_use").assertEvent();
|
events.expectRegister("test-user@localhost", "registerExistingUseremail").user((String) null).error("username_in_use").assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -89,12 +89,12 @@ public class RegisterTest {
|
||||||
loginPage.clickRegister();
|
loginPage.clickRegister();
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "email", "registerUserInvalidPasswordConfirm", "password", "invalid");
|
registerPage.register("firstName", "lastName", "registerUserInvalidPasswordConfirmemail", "registerUserInvalidPasswordConfirm", "password", "invalid");
|
||||||
|
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
Assert.assertEquals("Password confirmation doesn't match", registerPage.getError());
|
Assert.assertEquals("Password confirmation doesn't match", registerPage.getError());
|
||||||
|
|
||||||
events.expectRegister("registerUserInvalidPasswordConfirm", "email").user((String) null).error("invalid_registration").assertEvent();
|
events.expectRegister("registerUserInvalidPasswordConfirm", "registerUserInvalidPasswordConfirmemail").user((String) null).error("invalid_registration").assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -103,12 +103,12 @@ public class RegisterTest {
|
||||||
loginPage.clickRegister();
|
loginPage.clickRegister();
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "email", "registerUserMissingPassword", null, null);
|
registerPage.register("firstName", "lastName", "registerUserMissingPasswordemail", "registerUserMissingPassword", null, null);
|
||||||
|
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
Assert.assertEquals("Please specify password.", registerPage.getError());
|
Assert.assertEquals("Please specify password.", registerPage.getError());
|
||||||
|
|
||||||
events.expectRegister("registerUserMissingPassword", "email").user((String) null).error("invalid_registration").assertEvent();
|
events.expectRegister("registerUserMissingPassword", "registerUserMissingPasswordemail").user((String) null).error("invalid_registration").assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -125,17 +125,17 @@ public class RegisterTest {
|
||||||
loginPage.clickRegister();
|
loginPage.clickRegister();
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "email", "registerPasswordPolicy", "pass", "pass");
|
registerPage.register("firstName", "lastName", "registerPasswordPolicyemail", "registerPasswordPolicy", "pass", "pass");
|
||||||
|
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
Assert.assertEquals("Invalid password: minimum length 8", registerPage.getError());
|
Assert.assertEquals("Invalid password: minimum length 8", registerPage.getError());
|
||||||
|
|
||||||
events.expectRegister("registerPasswordPolicy", "email").user((String) null).error("invalid_registration").assertEvent();
|
events.expectRegister("registerPasswordPolicy", "registerPasswordPolicyemail").user((String) null).error("invalid_registration").assertEvent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "email", "registerPasswordPolicy", "password", "password");
|
registerPage.register("firstName", "lastName", "registerPasswordPolicyemail", "registerPasswordPolicy", "password", "password");
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
String userId = events.expectRegister("registerPasswordPolicy", "email").assertEvent().getUserId();
|
String userId = events.expectRegister("registerPasswordPolicy", "registerPasswordPolicyemail").assertEvent().getUserId();
|
||||||
|
|
||||||
events.expectLogin().user(userId).detail(Details.USERNAME, "registerPasswordPolicy").assertEvent();
|
events.expectLogin().user(userId).detail(Details.USERNAME, "registerPasswordPolicy").assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -154,12 +154,12 @@ public class RegisterTest {
|
||||||
loginPage.clickRegister();
|
loginPage.clickRegister();
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "email", null, "password", "password");
|
registerPage.register("firstName", "lastName", "registerUserMissingUsernameemail", null, "password", "password");
|
||||||
|
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
Assert.assertEquals("Please specify username", registerPage.getError());
|
Assert.assertEquals("Please specify username", registerPage.getError());
|
||||||
|
|
||||||
events.expectRegister(null, "email").removeDetail("username").error("invalid_registration").assertEvent();
|
events.expectRegister(null, "registerUserMissingUsernameemail").removeDetail("username").error("invalid_registration").assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -168,11 +168,11 @@ public class RegisterTest {
|
||||||
loginPage.clickRegister();
|
loginPage.clickRegister();
|
||||||
registerPage.assertCurrent();
|
registerPage.assertCurrent();
|
||||||
|
|
||||||
registerPage.register("firstName", "lastName", "email", "registerUserSuccess", "password", "password");
|
registerPage.register("firstName", "lastName", "registerUserSuccessemail", "registerUserSuccess", "password", "password");
|
||||||
|
|
||||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
String userId = events.expectRegister("registerUserSuccess", "email").assertEvent().getUserId();
|
String userId = events.expectRegister("registerUserSuccess", "registerUserSuccessemail").assertEvent().getUserId();
|
||||||
events.expectLogin().detail("username", "registerUserSuccess").user(userId).assertEvent();
|
events.expectLogin().detail("username", "registerUserSuccess").user(userId).assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
{
|
{
|
||||||
"username" : "REALM_COMPOSITE_1_USER",
|
"username" : "REALM_COMPOSITE_1_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user1@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
{
|
{
|
||||||
"username" : "REALM_ROLE_1_USER",
|
"username" : "REALM_ROLE_1_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user2@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
{
|
{
|
||||||
"username" : "REALM_APP_COMPOSITE_USER",
|
"username" : "REALM_APP_COMPOSITE_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user3@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
{
|
{
|
||||||
"username" : "REALM_APP_ROLE_USER",
|
"username" : "REALM_APP_ROLE_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user4@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
{
|
{
|
||||||
"username" : "APP_COMPOSITE_USER",
|
"username" : "APP_COMPOSITE_USER",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"email" : "test-user@localhost",
|
"email" : "test-user5@localhost",
|
||||||
"credentials" : [
|
"credentials" : [
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
|
|
Loading…
Reference in a new issue