keycloak-uncached

Details

diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 3b0b149..724d3b5 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -84,6 +84,8 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
 
     UserModel addUser(String username);
 
+    boolean deleteUser(String name);
+
     List<String> getDefaultRoles();
     
     void addDefaultRole(String name);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index d9c3a68..90d3341 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -2,6 +2,7 @@ package org.keycloak.models.jpa.entities;
 
 import org.keycloak.models.UserModel;
 
+import javax.persistence.CascadeType;
 import javax.persistence.CollectionTable;
 import javax.persistence.Column;
 import javax.persistence.ElementCollection;
@@ -33,6 +34,9 @@ import java.util.Set;
 })
 @Entity
 public class UserEntity {
+
+    public static final Class[] RELATIONSHIPS = new Class[] { ApplicationUserRoleMappingEntity.class, RealmUserRoleMappingEntity.class, SocialLinkEntity.class };
+
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     protected String id;
@@ -66,7 +70,7 @@ public class UserEntity {
     @CollectionTable
     protected Set<String> redirectUris = new HashSet<String>();
 
-    @OneToMany
+    @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
     protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
 
     public String getId() {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 416d5eb..0d4813a 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -11,6 +11,7 @@ import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.jpa.entities.ApplicationEntity;
+import org.keycloak.models.jpa.entities.ApplicationUserRoleMappingEntity;
 import org.keycloak.models.jpa.entities.CredentialEntity;
 import org.keycloak.models.jpa.entities.OAuthClientEntity;
 import org.keycloak.models.jpa.entities.RealmEntity;
@@ -457,6 +458,25 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean deleteUser(String name) {
+        TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserByLoginName", UserEntity.class);
+        query.setParameter("loginName", name);
+        query.setParameter("realm", realm);
+        List<UserEntity> results = query.getResultList();
+        if (results.size() == 0) return false;
+
+        UserEntity user = results.get(0);
+
+        for (Class r : UserEntity.RELATIONSHIPS) {
+            em.createQuery("delete from " + r.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
+        }
+
+        em.remove(user);
+
+        return true;
+    }
+
+    @Override
     public List<String> getDefaultRoles() {
         Collection<RoleEntity> entities = realm.getDefaultRoles();
         List<String> roles = new ArrayList<String>();
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
index 3279abf..83a6047 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/RealmAdapter.java
@@ -519,6 +519,16 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean deleteUser(String name) {
+        User user = findPicketlinkUser(name);
+        if (user == null) {
+            return false;
+        }
+        getIdm().remove(user);
+        return true;
+    }
+
+    @Override
     public RoleAdapter getRole(String name) {
         Role role = SampleModel.getRole(getIdm(), name);
         if (role == null) return null;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 830fef1..2f506b1 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -107,6 +107,13 @@ public class UsersResource {
         return new RealmManager(session).toRepresentation(user);
     }
 
+    @Path("{username}")
+    @DELETE
+    @NoCache
+    public void deleteUser(final @PathParam("username") String username) {
+        realm.deleteUser(username);
+    }
+
     @GET
     @NoCache
     @Produces("application/json")
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index 4810aa2..ce44f4c 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -4,11 +4,13 @@ import org.junit.Assert;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runners.MethodSorters;
+import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -174,6 +176,34 @@ public class AdapterTest extends AbstractKeycloakTest {
     }
 
     @Test
+    public void deleteUser() throws Exception {
+        test1CreateRealm();
+
+        UserModel user = realmModel.addUser("bburke");
+        user.setAttribute("attr1", "val1");
+        user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+
+        RoleModel testRole = realmModel.addRole("test");
+        realmModel.grantRole(user, testRole);
+
+        ApplicationModel app = realmModel.addApplication("test-app");
+        RoleModel appRole = app.addRole("test");
+        app.grantRole(user, appRole);
+
+        SocialLinkModel socialLink = new SocialLinkModel("google", user.getLoginName());
+        realmModel.addSocialLink(user, socialLink);
+
+        UserCredentialModel cred = new UserCredentialModel();
+        cred.setType(CredentialRepresentation.PASSWORD);
+        cred.setValue("password");
+        realmModel.updateCredential(user, cred);
+
+        Assert.assertTrue(realmModel.deleteUser("bburke"));
+        Assert.assertFalse(realmModel.deleteUser("bburke"));
+        Assert.assertNull(realmModel.getUser("bburke"));
+    }
+
+    @Test
     public void testUserSearch() throws Exception {
         test1CreateRealm();
         {