keycloak-aplcache

Details

diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/index.html b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
index f0c8996..5a1e6fc 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/index.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/index.html
@@ -64,13 +64,7 @@
 <div id="wrap">
     <div data-ng-include data-src="'partials/menu.html'"></div>
 
-    <div data-ng-view id="view" data-ng-hide="httpProviderError"></div>
-
-    <div id="httpProviderError" data-ng-show="httpProviderError">
-        <button class="btn btn-danger" data-ng-click="httpProviderError=null">
-            <strong>Error</strong> {{httpProviderError}}
-        </button>
-    </div>
+    <div data-ng-view id="view"></div>
 
     <div id="loading" class="loading-backdrop">
         <div class="loading">
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
index d97aed7..f010695 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
@@ -320,18 +320,21 @@ module.config(function($httpProvider) {
 
 });
 
-module.factory('errorInterceptor', function($q, $window, $rootScope, $location, Auth) {
+module.factory('errorInterceptor', function($q, $window, $rootScope, $location, Auth, Notifications) {
     return function(promise) {
         return promise.then(function(response) {
-            $rootScope.httpProviderError = null;
             return response;
         }, function(response) {
             if (response.status == 401) {
                 console.log('session timeout?');
                 Auth.loggedIn = false;
                 window.location = '/auth-server/rest/saas/login?path=' + $location.path();
-            } else {
-                $rootScope.httpProviderError = response.status;
+            } else if (response.status) {
+                if (response.data && response.data.errorMessage) {
+                    Notifications.error(response.data.errorMessage);
+                } else {
+                    Notifications.error("An unexpected server error has occurred");
+                }
             }
             return $q.reject(response);
         });
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
index 9d93d6f..8846801 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/applications.js
@@ -159,7 +159,7 @@ module.controller('ApplicationRoleDetailCtrl', function($scope, realm, applicati
             $scope.role.$remove({
                 realm : realm.id,
                 application : application.id,
-                role : $scope.role.name
+                roleId : $scope.role.id
             }, function() {
                 $location.url("/realms/" + realm.id + "/applications/" + application.id + "/roles");
                 Notifications.success("The role has been deleted.");
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
index 9271bfb..ad9bca4 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
@@ -23,7 +23,7 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
         Current.realms = data;
         if (data.length > 0) {
             Current.realm = data[0];
-            $location.url("/realms/" + Current.realm.id);
+            //$location.url("/realms/" + Current.realm.id);
         }
     });
 });
@@ -611,7 +611,7 @@ module.controller('RoleDetailCtrl', function($scope, realm, role, Role, $locatio
         Dialog.confirmDelete($scope.role.name, 'role', function() {
             $scope.role.$remove({
                 realm : realm.id,
-                role : $scope.role.name
+                roleId : $scope.role.id
             }, function() {
                 $location.url("/realms/" + realm.id + "/roles");
                 Notifications.success("The role has been deleted.");
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
index a147697..063ab84 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-role-detail.html
@@ -46,7 +46,7 @@
                             <label for="description">Description </label>
 
                             <div class="controls">
-                                <textarea rows="5" cols="50" id="description" name="description" data-ng-model="role.description" required></textarea>
+                                <textarea rows="5" cols="50" id="description" name="description" data-ng-model="role.description"></textarea>
                             
 <!-- Replaced by the textarea above <input type="text" id="description" name="description" data-ng-model="role.description" autofocus
                                        required> -->
diff --git a/core/src/main/java/org/keycloak/representations/idm/ErrorRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ErrorRepresentation.java
new file mode 100644
index 0000000..3b0aac1
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/ErrorRepresentation.java
@@ -0,0 +1,19 @@
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ErrorRepresentation {
+    public String errorMessage;
+
+    public ErrorRepresentation() {
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java b/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java
index 4442359..cf5748b 100755
--- a/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RoleContainerModel.java
@@ -11,6 +11,8 @@ public interface RoleContainerModel {
 
     RoleModel addRole(String name);
 
+    boolean removeRole(String id);
+
     List<RoleModel> getRoles();
 
     RoleModel getRoleById(String id);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index 00ed540..1ca34b6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -3,10 +3,7 @@ package org.keycloak.models.jpa;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.models.jpa.entities.ApplicationEntity;
-import org.keycloak.models.jpa.entities.ApplicationScopeMappingEntity;
-import org.keycloak.models.jpa.entities.ApplicationUserRoleMappingEntity;
-import org.keycloak.models.jpa.entities.RoleEntity;
+import org.keycloak.models.jpa.entities.*;
 
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
@@ -103,7 +100,7 @@ public class ApplicationAdapter implements ApplicationModel {
                 return new RoleAdapter(role);
             }
         }
-        return null;  //To change body of implemented methods use File | Settings | File Templates.
+        return null;
     }
 
     @Override
@@ -119,6 +116,26 @@ public class ApplicationAdapter implements ApplicationModel {
     }
 
     @Override
+    public boolean removeRole(String id) {
+        RoleEntity role = em.find(RoleEntity.class, id);
+        if (role == null) {
+            return false;
+        }
+
+        application.getRoles().remove(role);
+        application.getDefaultRoles().remove(role);
+
+        em.createQuery("delete from " + ApplicationScopeMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", role).executeUpdate();
+        em.createQuery("delete from " + ApplicationUserRoleMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", role).executeUpdate();
+        em.createQuery("delete from " + RealmScopeMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", role).executeUpdate();
+        em.createQuery("delete from " + RealmUserRoleMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", role).executeUpdate();
+
+        em.remove(role);
+
+        return true;
+    }
+
+    @Override
     public List<RoleModel> getRoles() {
         ArrayList<RoleModel> list = new ArrayList<RoleModel>();
         Collection<RoleEntity> roles = application.getRoles();
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 f3463c7..7f98fd0 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
@@ -10,18 +10,7 @@ import org.keycloak.models.RoleModel;
 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.ApplicationScopeMappingEntity;
-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;
-import org.keycloak.models.jpa.entities.RealmScopeMappingEntity;
-import org.keycloak.models.jpa.entities.RealmUserRoleMappingEntity;
-import org.keycloak.models.jpa.entities.RequiredCredentialEntity;
-import org.keycloak.models.jpa.entities.RoleEntity;
-import org.keycloak.models.jpa.entities.SocialLinkEntity;
-import org.keycloak.models.jpa.entities.UserEntity;
+import org.keycloak.models.jpa.entities.*;
 import org.keycloak.models.utils.SHAPasswordEncoder;
 import org.keycloak.models.utils.TimeBasedOTP;
 
@@ -800,6 +789,26 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean removeRole(String id) {
+        RoleEntity role = em.find(RoleEntity.class, id);
+        if (role == null) {
+            return false;
+        }
+
+        realm.getRoles().remove(role);
+        realm.getDefaultRoles().remove(role);
+
+        em.createQuery("delete from " + ApplicationScopeMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", role).executeUpdate();
+        em.createQuery("delete from " + ApplicationUserRoleMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", role).executeUpdate();
+        em.createQuery("delete from " + RealmScopeMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", role).executeUpdate();
+        em.createQuery("delete from " + RealmUserRoleMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", role).executeUpdate();
+
+        em.remove(role);
+
+        return true;
+    }
+
+    @Override
     public List<RoleModel> getRoles() {
         ArrayList<RoleModel> list = new ArrayList<RoleModel>();
         Collection<RoleEntity> roles = realm.getRoles();
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java
index 436b23c..98a510a 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java
@@ -5,6 +5,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.picketlink.mappings.ApplicationData;
 import org.keycloak.models.picketlink.relationships.ScopeRelationship;
+import org.picketlink.idm.IdentityManagementException;
 import org.picketlink.idm.IdentityManager;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.RelationshipManager;
@@ -155,6 +156,16 @@ public class ApplicationAdapter implements ApplicationModel {
     }
 
     @Override
+    public boolean removeRole(String id) {
+        try {
+            getIdm().remove(getIdm().lookupIdentityById(Role.class, id));
+            return true;
+        } catch (IdentityManagementException e) {
+            return false;
+        }
+    }
+
+    @Override
     public List<RoleModel> getRoles() {
         IdentityQuery<Role> query = getIdm().createIdentityQuery(Role.class);
         query.setParameter(Role.PARTITION, applicationData);
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 3767e65..3f6631c 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
@@ -21,6 +21,7 @@ import org.keycloak.models.picketlink.relationships.RequiredApplicationCredentia
 import org.keycloak.models.picketlink.relationships.RequiredCredentialRelationship;
 import org.keycloak.models.picketlink.relationships.ScopeRelationship;
 import org.keycloak.models.picketlink.relationships.SocialLinkRelationship;
+import org.picketlink.idm.IdentityManagementException;
 import org.picketlink.idm.IdentityManager;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.RelationshipManager;
@@ -553,6 +554,16 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean removeRole(String id) {
+        try {
+            getIdm().remove(getIdm().lookupIdentityById(Role.class, id));
+            return true;
+        } catch (IdentityManagementException e) {
+            return false;
+        }
+    }
+
+    @Override
     public List<RoleModel> getRoles() {
         IdentityManager idm = getIdm();
         IdentityQuery<Role> query = idm.createIdentityQuery(Role.class);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
index 1ab393f..1e70e6a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
@@ -5,16 +5,9 @@ import org.keycloak.models.Constants;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.resources.flows.Flows;
 
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.InternalServerErrorException;
-import javax.ws.rs.NotFoundException;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
+import javax.ws.rs.*;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
@@ -64,6 +57,15 @@ public class RoleContainerResource {
     }
 
     @Path("roles/{id}")
+    @DELETE
+    @NoCache
+    public void deleteRole(final @PathParam("id") String id) {
+        if (!roleContainer.removeRole(id)) {
+            throw new NotFoundException();
+        }
+    }
+
+    @Path("roles/{id}")
     @PUT
     @Consumes("application/json")
     public void updateRole(final @PathParam("id") String id, final RoleRepresentation rep) {
@@ -80,7 +82,7 @@ public class RoleContainerResource {
     @Consumes("application/json")
     public Response createRole(final @Context UriInfo uriInfo, final RoleRepresentation rep) {
         if (roleContainer.getRole(rep.getName()) != null || rep.getName().startsWith(Constants.INTERNAL_ROLE)) {
-            throw new InternalServerErrorException(); // todo appropriate status here.
+            return Flows.errors().exists("Role with name " + rep.getName() + " already exists");
         }
         RoleModel role = roleContainer.addRole(rep.getName());
         if (role == 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 2901c9c..61e70bf 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
@@ -9,12 +9,9 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.representations.idm.ApplicationMappingsRepresentation;
-import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.representations.idm.MappingsRepresentation;
-import org.keycloak.representations.idm.RoleRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.*;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.flows.Flows;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -71,7 +68,7 @@ public class UsersResource {
     @Consumes("application/json")
     public Response createUser(final @Context UriInfo uriInfo, final UserRepresentation rep) {
         if (realm.getUser(rep.getUsername()) != null) {
-            throw new InternalServerErrorException(); // todo appropriate status here.
+            return Flows.errors().exists("User with username " + rep.getUsername() + " already exists");
         }
         UserModel user = realm.addUser(rep.getUsername());
         if (user == null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/ErrorFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/ErrorFlows.java
new file mode 100644
index 0000000..5a96566
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/flows/ErrorFlows.java
@@ -0,0 +1,19 @@
+package org.keycloak.services.resources.flows;
+
+import org.keycloak.representations.idm.ErrorRepresentation;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ErrorFlows {
+
+    public Response exists(String message) {
+        ErrorRepresentation error = new ErrorRepresentation();
+        error.setErrorMessage(message);
+        return Response.status(Response.Status.CONFLICT).entity(error).type(MediaType.APPLICATION_JSON).build();
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
index 41aa04f..e46632a 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
@@ -45,4 +45,8 @@ public class Flows {
         return new OAuthFlows(realm, request, uriInfo, authManager, tokenManager);
     }
 
+    public static ErrorFlows errors() {
+        return new ErrorFlows();
+    }
+
 }
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index 7c2d405..e504c39 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -227,6 +227,33 @@ public class AdapterTest extends AbstractKeycloakTest {
         Assert.assertNull(realmModel.getApplicationById(app.getId()));
     }
 
+
+    @Test
+    public void testRemoveRole() throws Exception {
+        test1CreateRealm();
+
+        UserModel user = realmModel.addUser("bburke");
+
+        OAuthClientModel client = realmModel.addOAuthClient("client");
+
+        ApplicationModel app = realmModel.addApplication("test-app");
+
+        RoleModel appRole = app.addRole("test");
+        app.grantRole(user, appRole);
+        app.addScopeMapping(client.getOAuthAgent(), appRole);
+
+        RoleModel realmRole = realmModel.addRole("test");
+        realmModel.addScopeMapping(app.getApplicationUser(), realmRole);
+
+        Assert.assertTrue(realmModel.removeRole(realmRole.getId()));
+        Assert.assertFalse(realmModel.removeRole(realmRole.getId()));
+        Assert.assertNull(realmModel.getRole(realmRole.getName()));
+
+        Assert.assertTrue(app.removeRole(appRole.getId()));
+        Assert.assertFalse(app.removeRole(appRole.getId()));
+        Assert.assertNull(app.getRole(appRole.getName()));
+    }
+
     @Test
     public void testUserSearch() throws Exception {
         test1CreateRealm();