keycloak-memoizeit

Changes

model/api/src/main/java/org/keycloak/models/GrantedConsentModel.java 47(+0 -47)

Details

diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index fbea205..b4d93a7 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -20,9 +20,9 @@
         <class>org.keycloak.models.jpa.entities.IdentityProviderMapperEntity</class>
         <class>org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity</class>
         <class>org.keycloak.models.jpa.entities.ProtocolMapperEntity</class>
-        <class>org.keycloak.models.jpa.entities.GrantedConsentEntity</class>
-        <class>org.keycloak.models.jpa.entities.GrantedConsentRoleEntity</class>
-        <class>org.keycloak.models.jpa.entities.GrantedConsentProtocolMapperEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserConsentEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserConsentRoleEntity</class>
+        <class>org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity</class>
 
         <!-- JpaUserSessionProvider -->
         <class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.RC1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.RC1.xml
index a1698dc..7b8c877 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.RC1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.RC1.xml
@@ -35,7 +35,7 @@
         <addColumn tableName="CREDENTIAL">
             <column name="CREATED_DATE" type="BIGINT"/>
         </addColumn>
-        <createTable tableName="GRANTED_CONSENT">
+        <createTable tableName="USER_CONSENT">
             <column name="ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
@@ -46,16 +46,16 @@
                 <constraints nullable="false"/>
             </column>
         </createTable>
-        <createTable tableName="GRANTED_CONSENT_ROLE">
-            <column name="GRANTED_CONSENT_ID" type="VARCHAR(36)">
+        <createTable tableName="USER_CONSENT_ROLE">
+            <column name="USER_CONSENT_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
             <column name="ROLE_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
         </createTable>
-        <createTable tableName="GRANTED_CONSENT_PROT_MAPPER">
-            <column name="GRANTED_CONSENT_ID" type="VARCHAR(36)">
+        <createTable tableName="USER_CONSENT_PROT_MAPPER">
+            <column name="USER_CONSENT_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
             <column name="PROTOCOL_MAPPER_ID" type="VARCHAR(36)">
@@ -64,14 +64,14 @@
         </createTable>
         <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_IDPM" tableName="IDENTITY_PROVIDER_MAPPER"/>
         <addPrimaryKey columnNames="IDP_MAPPER_ID, NAME" constraintName="CONSTRAINT_IDPMConfig" tableName="IDP_MAPPER_CONFIG"/>
-        <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GRNTCSNT_PM" tableName="GRANTED_CONSENT"/>
-        <addPrimaryKey columnNames="GRANTED_CONSENT_ID, ROLE_ID" constraintName="CONSTRAINT_GRNTCSNT_ROLE_PM" tableName="GRANTED_CONSENT_ROLE"/>
-        <addPrimaryKey columnNames="GRANTED_CONSENT_ID, PROTOCOL_MAPPER_ID" constraintName="CONSTRAINT_GRNTCSNT_PRM_PM" tableName="GRANTED_CONSENT_PROT_MAPPER"/>
+        <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GRNTCSNT_PM" tableName="USER_CONSENT"/>
+        <addPrimaryKey columnNames="USER_CONSENT_ID, ROLE_ID" constraintName="CONSTRAINT_GRNTCSNT_ROLE_PM" tableName="USER_CONSENT_ROLE"/>
+        <addPrimaryKey columnNames="USER_CONSENT_ID, PROTOCOL_MAPPER_ID" constraintName="CONSTRAINT_GRNTCSNT_PRM_PM" tableName="USER_CONSENT_PROT_MAPPER"/>
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER_MAPPER" constraintName="FK_IDPM_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
         <addForeignKeyConstraint baseColumnNames="IDP_MAPPER_ID" baseTableName="IDP_MAPPER_CONFIG" constraintName="FK_IDPMConfig" referencedColumnNames="ID" referencedTableName="IDENTITY_PROVIDER_MAPPER"/>
-        <addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="GRANTED_CONSENT" constraintName="FK_GRNTCSNT_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
-        <addForeignKeyConstraint baseColumnNames="GRANTED_CONSENT_ID" baseTableName="GRANTED_CONSENT_ROLE" constraintName="FK_GRNTCSNT_ROLE_GR" referencedColumnNames="ID" referencedTableName="GRANTED_CONSENT"/>
-        <addForeignKeyConstraint baseColumnNames="GRANTED_CONSENT_ID" baseTableName="GRANTED_CONSENT_PROT_MAPPER" constraintName="FK_GRNTCSNT_PRM_GR" referencedColumnNames="ID" referencedTableName="GRANTED_CONSENT"/>
+        <addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_CONSENT" constraintName="FK_GRNTCSNT_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
+        <addForeignKeyConstraint baseColumnNames="USER_CONSENT_ID" baseTableName="USER_CONSENT_ROLE" constraintName="FK_GRNTCSNT_ROLE_GR" referencedColumnNames="ID" referencedTableName="USER_CONSENT"/>
+        <addForeignKeyConstraint baseColumnNames="USER_CONSENT_ID" baseTableName="USER_CONSENT_PROT_MAPPER" constraintName="FK_GRNTCSNT_PRM_GR" referencedColumnNames="ID" referencedTableName="USER_CONSENT"/>
 
         <addColumn tableName="CLIENT">
             <column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
@@ -101,7 +101,7 @@
 
         <dropUniqueConstraint tableName="KEYCLOAK_ROLE" constraintName="UK_J3RWUVD56ONTGSUHOGM184WW2"/>
         <addUniqueConstraint columnNames="NAME,CLIENT_REALM_CONSTRAINT" constraintName="UK_J3RWUVD56ONTGSUHOGM184WW2-2" tableName="KEYCLOAK_ROLE"/>
-        <addUniqueConstraint columnNames="CLIENT_ID, USER_ID" constraintName="UK_JKUWUVD56ONTGSUHOGM8UEWRT" tableName="GRANTED_CONSENT"/>
+        <addUniqueConstraint columnNames="CLIENT_ID, USER_ID" constraintName="UK_JKUWUVD56ONTGSUHOGM8UEWRT" tableName="USER_CONSENT"/>
 
     </changeSet>
 </databaseChangeLog>
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/context/MongoStoreInvocationContext.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/context/MongoStoreInvocationContext.java
index c542ffe..272f1c8 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/context/MongoStoreInvocationContext.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/context/MongoStoreInvocationContext.java
@@ -22,6 +22,8 @@ public interface MongoStoreInvocationContext {
 
     void beforeDBSearch(Class<? extends MongoIdentifiableEntity> entityType);
 
+    void beforeDBBulkUpdateOrRemove(Class<? extends MongoIdentifiableEntity> entityType);
+
     void begin();
 
     void commit();
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoStore.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoStore.java
index 36f448c..397c307 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoStore.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoStore.java
@@ -24,13 +24,35 @@ public interface MongoStore {
      */
     void updateEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context);
 
+    /**
+     * Bulk  update of more entities of some type
+     *
+     * @param type
+     * @param query
+     * @param update
+     * @param context
+     * @return count of updated entities
+     */
+    <T extends MongoIdentifiableEntity> int updateEntities(Class<T> type, DBObject query, DBObject update, MongoStoreInvocationContext context);
 
     <T extends MongoIdentifiableEntity> T loadEntity(Class<T> type, String id, MongoStoreInvocationContext context);
 
     <T extends MongoIdentifiableEntity> T loadSingleEntity(Class<T> type, DBObject query, MongoStoreInvocationContext context);
 
+    /**
+     * @param type
+     * @param query
+     * @param context
+     * @return query result or empty list if no results available for the query. Doesn't return null
+     */
     <T extends MongoIdentifiableEntity> List<T> loadEntities(Class<T> type, DBObject query, MongoStoreInvocationContext context);
 
+    /**
+     * @param type
+     * @param query
+     * @param context
+     * @return query result or empty list if no results available for the query. Doesn't return null
+     */
     <T extends MongoIdentifiableEntity> List<T> loadEntities(Class<T> type, DBObject query, DBObject sort, int firstResult, int maxResults, MongoStoreInvocationContext context);
 
     <T extends MongoIdentifiableEntity> int countEntities(Class<T> type, DBObject query, MongoStoreInvocationContext context);
@@ -39,7 +61,16 @@ public interface MongoStore {
 
     boolean removeEntity(Class<? extends MongoIdentifiableEntity> type, String id, MongoStoreInvocationContext context);
 
-    boolean removeEntities(Class<? extends MongoIdentifiableEntity> type, DBObject query, MongoStoreInvocationContext context);
+    /**
+     *
+     * @param type
+     * @param query
+     * @param callback if true, then store will first load all entities, then call "afterRemove" for every entity. If false, the entities are removed directly without load and calling "afterRemove" callback
+     *                 false has better performance (especially if we are going to remove big number of entities)
+     * @param context
+     * @return count of removed entities
+     */
+    int removeEntities(Class<? extends MongoIdentifiableEntity> type, DBObject query, boolean callback, MongoStoreInvocationContext context);
 
     <S> boolean pushItemToList(MongoIdentifiableEntity entity, String listPropertyName, S itemToPush, boolean skipIfAlreadyPresent, MongoStoreInvocationContext context);
 
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index 138955b..0ef0069 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -38,7 +38,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
             "org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity",
             "org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity",
             "org.keycloak.models.entities.UserFederationProviderEntity",
-            "org.keycloak.models.entities.ProtocolMapperEntity"
+            "org.keycloak.models.entities.ProtocolMapperEntity",
+            "org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity"
     };
 
     private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/context/SimpleMongoStoreInvocationContext.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/context/SimpleMongoStoreInvocationContext.java
index 0ecffc6..868805b 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/context/SimpleMongoStoreInvocationContext.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/context/SimpleMongoStoreInvocationContext.java
@@ -46,6 +46,10 @@ public class SimpleMongoStoreInvocationContext implements MongoStoreInvocationCo
     }
 
     @Override
+    public void beforeDBBulkUpdateOrRemove(Class<? extends MongoIdentifiableEntity> entityType) {
+    }
+
+    @Override
     public void begin() {
     }
 
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/context/TransactionMongoStoreInvocationContext.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/context/TransactionMongoStoreInvocationContext.java
index 39cf9eb..117f07f 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/context/TransactionMongoStoreInvocationContext.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/context/TransactionMongoStoreInvocationContext.java
@@ -50,10 +50,6 @@ public class TransactionMongoStoreInvocationContext implements MongoStoreInvocat
 
     @Override
     public void addUpdateTask(MongoIdentifiableEntity entityToUpdate, MongoTask task) {
-        if (!loadedObjects.containsValue(entityToUpdate)) {
-            throw new IllegalStateException("Entity " + entityToUpdate + " not found in loaded objects");
-        }
-
         Set<MongoTask> currentObjectTasks = pendingUpdateTasks.get(entityToUpdate);
         if (currentObjectTasks == null) {
             currentObjectTasks = new LinkedHashSet<MongoTask>();
@@ -107,6 +103,24 @@ public class TransactionMongoStoreInvocationContext implements MongoStoreInvocat
     }
 
     @Override
+    public void beforeDBBulkUpdateOrRemove(Class<? extends MongoIdentifiableEntity> entityType) {
+        beforeDBSearch(entityType);
+        Set<String> toRemove = new HashSet<String>();
+
+        for (Map.Entry<String, MongoIdentifiableEntity> entry : loadedObjects.entrySet()) {
+            MongoIdentifiableEntity entity =  entry.getValue();
+            if (entity.getClass().equals(entityType)) {
+                toRemove.add(entry.getKey());
+            }
+        }
+
+        // Now remove all loadedObjects
+        for (String objectId : toRemove) {
+            loadedObjects.remove(objectId);
+        }
+    }
+
+    @Override
     public void begin() {
         loadedObjects.clear();
         pendingUpdateTasks.clear();
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java
index aded77e..1696b65 100755
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java
@@ -7,6 +7,7 @@ import com.mongodb.DBCollection;
 import com.mongodb.DBCursor;
 import com.mongodb.DBObject;
 import com.mongodb.MongoException;
+import com.mongodb.WriteResult;
 import org.jboss.logging.Logger;
 import org.keycloak.connections.mongo.api.MongoCollection;
 import org.keycloak.connections.mongo.api.MongoEntity;
@@ -127,7 +128,7 @@ public class MongoStoreImpl implements MongoStore {
             throw convertException(e);
         }
 
-        // Treat object as created in this transaction (It is already submited to transaction)
+        // Treat object as created in this transaction (It is already submitted to transaction)
         context.addCreatedEntity(entity);
     }
 
@@ -170,6 +171,16 @@ public class MongoStoreImpl implements MongoStore {
         context.addUpdateTask(entity, fullUpdateTask);
     }
 
+    @Override
+    public <T extends MongoIdentifiableEntity> int updateEntities(Class<T> type, DBObject query, DBObject update, MongoStoreInvocationContext context) {
+        context.beforeDBBulkUpdateOrRemove(type);
+
+        DBCollection collection = getDBCollectionForType(type);
+        WriteResult wr = collection.update(query, update, false, true);
+
+        logger.debugf("Updated %d collections of type %s", wr.getN(), type);
+        return wr.getN();
+    }
 
     @Override
     public <T extends MongoIdentifiableEntity> T loadEntity(Class<T> type, String id, MongoStoreInvocationContext context) {
@@ -275,19 +286,32 @@ public class MongoStoreImpl implements MongoStore {
 
 
     @Override
-    public boolean removeEntities(Class<? extends MongoIdentifiableEntity> type, DBObject query, MongoStoreInvocationContext context) {
-        List<? extends MongoIdentifiableEntity> foundObjects = loadEntities(type, query, context);
-        if (foundObjects.size() == 0) {
-            return false;
+    public int removeEntities(Class<? extends MongoIdentifiableEntity> type, DBObject query, boolean callback, MongoStoreInvocationContext context) {
+        if (callback) {
+            List<? extends MongoIdentifiableEntity> foundObjects = loadEntities(type, query, context);
+            if (foundObjects.size() == 0) {
+                return 0;
+            } else {
+                DBCollection dbCollection = getDBCollectionForType(type);
+                dbCollection.remove(query);
+
+                logger.debugf("Removed %d entities of type: %s, query: %s", foundObjects.size(), type, query);
+
+                for (MongoIdentifiableEntity found : foundObjects) {
+                    context.addRemovedEntity(found);;
+                }
+                return foundObjects.size();
+            }
         } else {
+
+            context.beforeDBBulkUpdateOrRemove(type);
+
             DBCollection dbCollection = getDBCollectionForType(type);
-            dbCollection.remove(query);
-            //logger.debug("Removed %d" + foundObjects.size() + " entities of type: " + type + ", query: " + query);
+            WriteResult writeResult = dbCollection.remove(query);
+            int removedCount = writeResult.getN();
 
-            for (MongoIdentifiableEntity found : foundObjects) {
-                context.addRemovedEntity(found);;
-            }
-            return true;
+            logger.debugf("Removed directly %d entities of type: %s, query: %s", removedCount, type, query);
+            return removedCount;
         }
     }
 
diff --git a/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_2_0_RC1.java b/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_2_0_RC1.java
index c980067..8ee4db3 100644
--- a/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_2_0_RC1.java
+++ b/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_2_0_RC1.java
@@ -22,6 +22,7 @@ public class Update1_2_0_RC1 extends Update {
 
         db.getCollection("realms").update(new BasicDBObject(), new BasicDBObject("$rename", new BasicDBObject("adminAppId", "clientId")), false, true);
 
+        ensureIndex("userConsents", new String[]{"clientId", "userId"}, true, false);
     }
 
     private void convertApplicationsToClients() {
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserConsentRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserConsentRepresentation.java
new file mode 100644
index 0000000..113ba84
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/UserConsentRepresentation.java
@@ -0,0 +1,28 @@
+package org.keycloak.representations.idm;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserConsentRepresentation {
+
+    protected List<String> grantedRoles;           // points to roleIds
+    protected List<String> grantedProtocolMappers; // points to protocolMapperIds
+
+    public List<String> getGrantedRoles() {
+        return grantedRoles;
+    }
+
+    public void setGrantedRoles(List<String> grantedRoles) {
+        this.grantedRoles = grantedRoles;
+    }
+
+    public List<String> getGrantedProtocolMappers() {
+        return grantedProtocolMappers;
+    }
+
+    public void setGrantedProtocolMappers(List<String> grantedProtocolMappers) {
+        this.grantedProtocolMappers = grantedProtocolMappers;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index 94a90d9..b9716d3 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -27,6 +27,7 @@ public class UserRepresentation {
     protected List<FederatedIdentityRepresentation> federatedIdentities;
     protected List<String> realmRoles;
     protected Map<String, List<String>> clientRoles;
+    protected Map<String, UserConsentRepresentation> clientConsents;
 
     @Deprecated
     protected Map<String, List<String>> applicationRoles;
@@ -176,6 +177,14 @@ public class UserRepresentation {
         this.clientRoles = clientRoles;
     }
 
+    public Map<String, UserConsentRepresentation> getClientConsents() {
+        return clientConsents;
+    }
+
+    public void setClientConsents(Map<String, UserConsentRepresentation> clientConsents) {
+        this.clientConsents = clientConsents;
+    }
+
     @Deprecated
     public Map<String, List<String>> getApplicationRoles() {
         return applicationRoles;
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index c404c3d..bccce2d 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -8,14 +8,15 @@ import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.SerializationConfig;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.ModelToRepresentation;
-import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
@@ -23,6 +24,7 @@ import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.RolesRepresentation;
 import org.keycloak.representations.idm.ScopeMappingRepresentation;
 import org.keycloak.representations.idm.FederatedIdentityRepresentation;
+import org.keycloak.representations.idm.UserConsentRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 
 import java.io.IOException;
@@ -283,6 +285,35 @@ public class ExportUtils {
         userRep.setCredentials(credReps);
         userRep.setFederationLink(user.getFederationLink());
 
+        // Grants
+        List<UserConsentModel> consents = user.getConsents();
+        Map<String, UserConsentRepresentation> consentReps = new HashMap<String, UserConsentRepresentation>();
+        for (UserConsentModel consent : consents) {
+            String clientId = consent.getClient().getClientId();
+
+            List<String> grantedProtocolMappers = new LinkedList<String>();
+            for (ProtocolMapperModel protocolMapper : consent.getGrantedProtocolMappers()) {
+                grantedProtocolMappers.add(protocolMapper.getId());
+            }
+
+            List<String> grantedRoles = new LinkedList<String>();
+            for (RoleModel role : consent.getGrantedRoles()) {
+                grantedRoles.add(role.getId());
+            }
+
+
+            if (grantedRoles.size() > 0 || grantedProtocolMappers.size() > 0) {
+                UserConsentRepresentation consentRep = new UserConsentRepresentation();
+                if (grantedRoles.size() > 0) consentRep.setGrantedRoles(grantedRoles);
+                if (grantedProtocolMappers.size() > 0) consentRep.setGrantedProtocolMappers(grantedProtocolMappers);
+                consentReps.put(clientId, consentRep);
+            }
+        }
+
+        if (consentReps.size() > 0) {
+            userRep.setClientConsents(consentReps);
+        }
+
         return userRep;
     }
 
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccessBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccessBean.java
index 58cea2f..67eb4a6 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccessBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccessBean.java
@@ -5,7 +5,7 @@ import java.util.LinkedList;
 import java.util.List;
 
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.GrantedConsentModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
@@ -20,14 +20,13 @@ public class AccessBean {
     private List<ClientGrantBean> clientGrants = new LinkedList<ClientGrantBean>();
 
     public AccessBean(RealmModel realm, UserModel user, URI baseUri, String stateChecker) {
-        List<GrantedConsentModel> grantedConsents = user.getGrantedConsents();
-        for (GrantedConsentModel consent : grantedConsents) {
-            ClientModel client = realm.getClientById(consent.getClientId());
+        List<UserConsentModel> grantedConsents = user.getConsents();
+        for (UserConsentModel consent : grantedConsents) {
+            ClientModel client = consent.getClient();
 
             List<RoleModel> realmRolesGranted = new LinkedList<RoleModel>();
             MultivaluedHashMap<String, RoleModel> resourceRolesGranted = new MultivaluedHashMap<String, RoleModel>();
-            for (String roleId : consent.getGrantedRoles()) {
-                RoleModel role = realm.getRoleById(roleId);
+            for (RoleModel role : consent.getGrantedRoles()) {
                 if (role.getContainer() instanceof RealmModel) {
                     realmRolesGranted.add(role);
                 } else {
@@ -36,8 +35,7 @@ public class AccessBean {
             }
 
             List<String> claimsGranted = new LinkedList<String>();
-            for (String protocolMapperId : consent.getGrantedProtocolMappers()) {
-                ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protocolMapperId);
+            for (ProtocolMapperModel protocolMapper : consent.getGrantedProtocolMappers()) {
                 claimsGranted.add(protocolMapper.getConsentText());
             }
 
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserConsentEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserConsentEntity.java
new file mode 100644
index 0000000..47b76c6
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserConsentEntity.java
@@ -0,0 +1,47 @@
+package org.keycloak.models.entities;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserConsentEntity extends AbstractIdentifiableEntity {
+
+    private String userId;
+    private String clientId;
+    private List<String> grantedRoles = new ArrayList<String>();
+    private List<String> grantedProtocolMappers = new ArrayList<String>();
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public List<String> getGrantedRoles() {
+        return grantedRoles;
+    }
+
+    public void setGrantedRoles(List<String> grantedRoles) {
+        this.grantedRoles = grantedRoles;
+    }
+
+    public List<String> getGrantedProtocolMappers() {
+        return grantedProtocolMappers;
+    }
+
+    public void setGrantedProtocolMappers(List<String> grantedProtocolMappers) {
+        this.grantedProtocolMappers = grantedProtocolMappers;
+    }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/UserConsentModel.java b/model/api/src/main/java/org/keycloak/models/UserConsentModel.java
new file mode 100644
index 0000000..11f1034
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/UserConsentModel.java
@@ -0,0 +1,69 @@
+package org.keycloak.models;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserConsentModel {
+
+    private final RealmModel realm;
+    private final ClientModel client;
+    private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
+    private Set<RoleModel> roles = new HashSet<RoleModel>();
+
+    public UserConsentModel(RealmModel realm, String clientId) {
+        this.realm = realm;
+        this.client = realm.getClientById(clientId);
+
+        if (client == null) {
+            throw new ModelException("Client with id [" + clientId + "] is not available");
+        }
+    }
+
+    public ClientModel getClient() {
+        return client;
+    }
+
+    public void addGrantedRole(String roleId) {
+        RoleModel role = realm.getRoleById(roleId);
+
+        // Chance that role was already deleted by other transaction and is not available anymore
+        if (role != null) {
+            roles.add(role);
+        }
+    }
+
+    public Set<RoleModel> getGrantedRoles() {
+        return roles;
+    }
+
+    public boolean isRoleGranted(RoleModel role) {
+        for (RoleModel currentRole : roles) {
+            if (currentRole.getId().equals(role.getId())) return true;
+        }
+        return false;
+    }
+
+    public void addGrantedProtocolMapper(String protocolMapperId) {
+        ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protocolMapperId);
+
+        // Chance that protocolMapper was already deleted by other transaction and is not available anymore
+        if (protocolMapper != null) {
+            protocolMappers.add(protocolMapper);
+        }
+    }
+
+    public Set<ProtocolMapperModel> getGrantedProtocolMappers() {
+        return protocolMappers;
+    }
+
+    public boolean isProtocolMapperGranted(ProtocolMapperModel protocolMapper) {
+        for (ProtocolMapperModel currentProtMapper : protocolMappers) {
+            if (currentProtMapper.getId().equals(protocolMapper.getId())) return true;
+        }
+        return false;
+    }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 6ad716c..ed5ae9e 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -75,11 +75,11 @@ public interface UserModel {
     String getFederationLink();
     void setFederationLink(String link);
 
-    GrantedConsentModel addGrantedConsent(GrantedConsentModel consent);
-    GrantedConsentModel getGrantedConsentByClient(String clientId);
-    List<GrantedConsentModel> getGrantedConsents();
-    void updateGrantedConsent(GrantedConsentModel consent);
-    boolean revokeGrantedConsentForClient(String clientId);
+    void addConsent(UserConsentModel consent);
+    UserConsentModel getConsentByClient(String clientId);
+    List<UserConsentModel> getConsents();
+    void updateConsent(UserConsentModel consent);
+    boolean revokeConsentForClient(String clientId);
 
     public static enum RequiredAction {
         VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 6fc3e1e..0312ff4 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -16,6 +16,7 @@ import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserFederationProviderModel;
@@ -34,6 +35,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
 import org.keycloak.representations.idm.ScopeMappingRepresentation;
 import org.keycloak.representations.idm.SocialLinkRepresentation;
+import org.keycloak.representations.idm.UserConsentRepresentation;
 import org.keycloak.representations.idm.UserFederationProviderRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.util.UriUtils;
@@ -789,6 +791,35 @@ public class RepresentationToModel {
                 createClientRoleMappings(client, user, entry.getValue());
             }
         }
+        if (userRep.getClientConsents() != null) {
+            for (Map.Entry<String, UserConsentRepresentation> entry : userRep.getClientConsents().entrySet()) {
+                ClientModel client = clientMap.get(entry.getKey());
+                if (client == null) {
+                    throw new RuntimeException("Unable to find client consent mappings for client: " + entry.getKey());
+                }
+
+                UserConsentModel consentModel = new UserConsentModel(newRealm, client.getId());
+
+                UserConsentRepresentation consentRep = entry.getValue();
+                if (consentRep.getGrantedRoles() != null) {
+                    for (String roleId : consentRep.getGrantedRoles()) {
+                        if (newRealm.getRoleById(roleId) == null) {
+                            throw new RuntimeException("Unable to find realm role referenced in consent mappings of user " + user.getUsername() + ". Role ID: " + roleId);
+                        }
+                        consentModel.addGrantedRole(roleId);
+                    }
+                }
+                if (consentRep.getGrantedProtocolMappers() != null) {
+                    for (String mapperId : consentRep.getGrantedProtocolMappers()) {
+                        if (client.getProtocolMapperById(mapperId) == null) {
+                            throw new RuntimeException("Unable to find protocol mapper referenced in consent mappings of user " + user.getUsername() + ". Protocol mapper ID: " + mapperId);
+                        }
+                        consentModel.addGrantedProtocolMapper(mapperId);;
+                    }
+                }
+                user.addConsent(consentModel);
+            }
+        }
         return user;
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index 83b6add..8ac8f5b 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -1,7 +1,7 @@
 package org.keycloak.models.utils;
 
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.GrantedConsentModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
@@ -188,27 +188,27 @@ public class UserModelDelegate implements UserModel {
     }
 
     @Override
-    public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
-        return delegate.addGrantedConsent(consent);
+    public void addConsent(UserConsentModel consent) {
+        delegate.addConsent(consent);
     }
 
     @Override
-    public GrantedConsentModel getGrantedConsentByClient(String clientId) {
-        return delegate.getGrantedConsentByClient(clientId);
+    public UserConsentModel getConsentByClient(String clientId) {
+        return delegate.getConsentByClient(clientId);
     }
 
     @Override
-    public List<GrantedConsentModel> getGrantedConsents() {
-        return delegate.getGrantedConsents();
+    public List<UserConsentModel> getConsents() {
+        return delegate.getConsents();
     }
 
     @Override
-    public void updateGrantedConsent(GrantedConsentModel consent) {
-        delegate.updateGrantedConsent(consent);
+    public void updateConsent(UserConsentModel consent) {
+        delegate.updateConsent(consent);
     }
 
     @Override
-    public boolean revokeGrantedConsentForClient(String clientId) {
-        return delegate.revokeGrantedConsentForClient(clientId);
+    public boolean revokeConsentForClient(String clientId) {
+        return delegate.revokeConsentForClient(clientId);
     }
 }
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java
index 18cc9c8..34ddb06 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java
@@ -277,7 +277,8 @@ public class ClientAdapter implements ClientModel {
             throw new RuntimeException("protocol mapper name must be unique per protocol");
         }
         ProtocolMapperEntity entity = new ProtocolMapperEntity();
-        entity.setId(KeycloakModelUtils.generateId());
+        String id = model.getId() != null ? model.getId() : KeycloakModelUtils.generateId();
+        entity.setId(id);
         entity.setProtocol(model.getProtocol());
         entity.setName(model.getName());
         entity.setProtocolMapper(model.getProtocolMapper());
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index 3033eea..0937e2c 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
@@ -20,7 +20,7 @@ import org.keycloak.models.ClientModel;
 
 import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
 
-import org.keycloak.models.GrantedConsentModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
@@ -432,30 +432,29 @@ public class UserAdapter implements UserModel, Comparable {
     }
 
     @Override
-    public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
+    public void addConsent(UserConsentModel consent) {
         // TODO
-        return null;
     }
 
     @Override
-    public GrantedConsentModel getGrantedConsentByClient(String clientId) {
+    public UserConsentModel getConsentByClient(String clientId) {
         // TODO
         return null;
     }
 
     @Override
-    public List<GrantedConsentModel> getGrantedConsents() {
+    public List<UserConsentModel> getConsents() {
         // TODO
         return null;
     }
 
     @Override
-    public void updateGrantedConsent(GrantedConsentModel consent) {
+    public void updateConsent(UserConsentModel consent) {
         // TODO
     }
 
     @Override
-    public boolean revokeGrantedConsentForClient(String clientId) {
+    public boolean revokeConsentForClient(String clientId) {
         // TODO
         return false;
     }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
index 97b62fc..10336fd 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
@@ -1,7 +1,7 @@
 package org.keycloak.models.cache;
 
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.GrantedConsentModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleContainerModel;
@@ -277,34 +277,34 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
+    public void addConsent(UserConsentModel consent) {
         getDelegateForUpdate();
-        return updated.addGrantedConsent(consent);
+        updated.addConsent(consent);
     }
 
     @Override
-    public GrantedConsentModel getGrantedConsentByClient(String clientId) {
+    public UserConsentModel getConsentByClient(String clientId) {
         // TODO: caching?
         getDelegateForUpdate();
-        return updated.getGrantedConsentByClient(clientId);
+        return updated.getConsentByClient(clientId);
     }
 
     @Override
-    public List<GrantedConsentModel> getGrantedConsents() {
+    public List<UserConsentModel> getConsents() {
         // TODO: caching?
         getDelegateForUpdate();
-        return updated.getGrantedConsents();
+        return updated.getConsents();
     }
 
     @Override
-    public void updateGrantedConsent(GrantedConsentModel consent) {
+    public void updateConsent(UserConsentModel consent) {
         getDelegateForUpdate();
-        updated.updateGrantedConsent(consent);
+        updated.updateConsent(consent);
     }
 
     @Override
-    public boolean revokeGrantedConsentForClient(String clientId) {
+    public boolean revokeConsentForClient(String clientId) {
         getDelegateForUpdate();
-        return updated.revokeGrantedConsentForClient(clientId);
+        return updated.revokeConsentForClient(clientId);
     }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 498db9a..f9f62b6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -378,7 +378,7 @@ public class ClientAdapter implements ClientModel {
         if (getProtocolMapperByName(model.getProtocol(), model.getName()) != null) {
             throw new RuntimeException("protocol mapper name must be unique per protocol");
         }
-        String id = KeycloakModelUtils.generateId();
+        String id = model.getId() != null ? model.getId() : KeycloakModelUtils.generateId();
         ProtocolMapperEntity entity = new ProtocolMapperEntity();
         entity.setId(id);
         entity.setName(model.getName());
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 15300d0..d0af512 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -88,9 +88,9 @@ public class JpaUserProvider implements UserProvider {
     private void removeUser(UserEntity user) {
         em.createNamedQuery("deleteUserRoleMappingsByUser").setParameter("user", user).executeUpdate();
         em.createNamedQuery("deleteFederatedIdentityByUser").setParameter("user", user).executeUpdate();
-        em.createNamedQuery("deleteGrantedConsentRolesByUser").setParameter("user", user).executeUpdate();
-        em.createNamedQuery("deleteGrantedConsentProtMappersByUser").setParameter("user", user).executeUpdate();
-        em.createNamedQuery("deleteGrantedConsentsByUser").setParameter("user", user).executeUpdate();
+        em.createNamedQuery("deleteUserConsentRolesByUser").setParameter("user", user).executeUpdate();
+        em.createNamedQuery("deleteUserConsentProtMappersByUser").setParameter("user", user).executeUpdate();
+        em.createNamedQuery("deleteUserConsentsByUser").setParameter("user", user).executeUpdate();
         em.remove(user);
     }
 
@@ -134,11 +134,11 @@ public class JpaUserProvider implements UserProvider {
 
     @Override
     public void preRemove(RealmModel realm) {
-        int num = em.createNamedQuery("deleteGrantedConsentRolesByRealm")
+        int num = em.createNamedQuery("deleteUserConsentRolesByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
-        num = em.createNamedQuery("deleteGrantedConsentProtMappersByRealm")
+        num = em.createNamedQuery("deleteUserConsentProtMappersByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
-        num = em.createNamedQuery("deleteGrantedConsentsByRealm")
+        num = em.createNamedQuery("deleteUserConsentsByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteUserRoleMappingsByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
@@ -184,20 +184,20 @@ public class JpaUserProvider implements UserProvider {
 
     @Override
     public void preRemove(RealmModel realm, RoleModel role) {
-        em.createNamedQuery("deleteGrantedConsentRolesByRole").setParameter("roleId", role.getId()).executeUpdate();
+        em.createNamedQuery("deleteUserConsentRolesByRole").setParameter("roleId", role.getId()).executeUpdate();
         em.createNamedQuery("deleteUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
     }
 
     @Override
     public void preRemove(RealmModel realm, ClientModel client) {
-        em.createNamedQuery("deleteGrantedConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
-        em.createNamedQuery("deleteGrantedConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
-        em.createNamedQuery("deleteGrantedConsentsByClient").setParameter("clientId", client.getId()).executeUpdate();
+        em.createNamedQuery("deleteUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
+        em.createNamedQuery("deleteUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
+        em.createNamedQuery("deleteUserConsentsByClient").setParameter("clientId", client.getId()).executeUpdate();
     }
 
     @Override
     public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
-        em.createNamedQuery("deleteGrantedConsentProtMappersByProtocolMapper")
+        em.createNamedQuery("deleteUserConsentProtMappersByProtocolMapper")
                 .setParameter("protocolMapperId", protocolMapper.getId())
                 .executeUpdate();
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index f8d9352..737e0e3 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -1,7 +1,8 @@
 package org.keycloak.models.jpa;
 
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.GrantedConsentModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.PasswordPolicy;
@@ -12,9 +13,9 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.jpa.entities.CredentialEntity;
-import org.keycloak.models.jpa.entities.GrantedConsentEntity;
-import org.keycloak.models.jpa.entities.GrantedConsentProtocolMapperEntity;
-import org.keycloak.models.jpa.entities.GrantedConsentRoleEntity;
+import org.keycloak.models.jpa.entities.UserConsentEntity;
+import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
+import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
 import org.keycloak.models.jpa.entities.UserAttributeEntity;
 import org.keycloak.models.jpa.entities.UserEntity;
 import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
@@ -29,7 +30,6 @@ import javax.persistence.TypedQuery;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Date;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -480,18 +480,15 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
-        String clientId = consent.getClientId();
-        if (clientId == null) {
-            throw new ModelException("clientId needs to be filled for newly added consent!");
-        }
+    public void addConsent(UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
 
-        GrantedConsentEntity consentEntity = getGrantedConsentEntity(clientId);
+        UserConsentEntity consentEntity = getGrantedConsentEntity(clientId);
         if (consentEntity != null) {
             throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
         }
 
-        consentEntity = new GrantedConsentEntity();
+        consentEntity = new UserConsentEntity();
         consentEntity.setId(KeycloakModelUtils.generateId());
         consentEntity.setUser(user);
         consentEntity.setClientId(clientId);
@@ -499,38 +496,33 @@ public class UserAdapter implements UserModel {
         em.flush();
 
         updateGrantedConsentEntity(consentEntity, consent);
-
-        return consent;
     }
 
     @Override
-    public GrantedConsentModel getGrantedConsentByClient(String clientId) {
-        GrantedConsentEntity entity = getGrantedConsentEntity(clientId);
+    public UserConsentModel getConsentByClient(String clientId) {
+        UserConsentEntity entity = getGrantedConsentEntity(clientId);
         return toConsentModel(entity);
     }
 
     @Override
-    public List<GrantedConsentModel> getGrantedConsents() {
-        TypedQuery<GrantedConsentEntity> query = em.createNamedQuery("grantedConsentsByUser", GrantedConsentEntity.class);
+    public List<UserConsentModel> getConsents() {
+        TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentsByUser", UserConsentEntity.class);
         query.setParameter("userId", getId());
-        List<GrantedConsentEntity> results = query.getResultList();
+        List<UserConsentEntity> results = query.getResultList();
 
-        List<GrantedConsentModel> consents = new ArrayList<GrantedConsentModel>();
-        for (GrantedConsentEntity entity : results) {
-            GrantedConsentModel model = toConsentModel(entity);
+        List<UserConsentModel> consents = new ArrayList<UserConsentModel>();
+        for (UserConsentEntity entity : results) {
+            UserConsentModel model = toConsentModel(entity);
             consents.add(model);
         }
         return consents;
     }
 
     @Override
-    public void updateGrantedConsent(GrantedConsentModel consent) {
-        String clientId = consent.getClientId();
-        if (clientId == null) {
-            throw new ModelException("clientId needs to be for newly added consent!");
-        }
+    public void updateConsent(UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
 
-        GrantedConsentEntity consentEntity = getGrantedConsentEntity(clientId);
+        UserConsentEntity consentEntity = getGrantedConsentEntity(clientId);
         if (consentEntity == null) {
             throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
         }
@@ -539,8 +531,8 @@ public class UserAdapter implements UserModel {
     }
 
     @Override
-    public boolean revokeGrantedConsentForClient(String clientId) {
-        GrantedConsentEntity consentEntity = getGrantedConsentEntity(clientId);
+    public boolean revokeConsentForClient(String clientId) {
+        UserConsentEntity consentEntity = getGrantedConsentEntity(clientId);
         if (consentEntity == null) return false;
 
         em.remove(consentEntity);
@@ -549,11 +541,11 @@ public class UserAdapter implements UserModel {
     }
 
 
-    private GrantedConsentEntity getGrantedConsentEntity(String clientId) {
-        TypedQuery<GrantedConsentEntity> query = em.createNamedQuery("grantedConsentByUserAndClient", GrantedConsentEntity.class);
+    private UserConsentEntity getGrantedConsentEntity(String clientId) {
+        TypedQuery<UserConsentEntity> query = em.createNamedQuery("userConsentByUserAndClient", UserConsentEntity.class);
         query.setParameter("userId", getId());
         query.setParameter("clientId", clientId);
-        List<GrantedConsentEntity> results = query.getResultList();
+        List<UserConsentEntity> results = query.getResultList();
         if (results.size() > 1) {
             throw new ModelException("More results found for user [" + getUsername() + "] and client [" + clientId + "]");
         } else if (results.size() == 1) {
@@ -563,23 +555,23 @@ public class UserAdapter implements UserModel {
         }
     }
 
-    private GrantedConsentModel toConsentModel(GrantedConsentEntity entity) {
+    private UserConsentModel toConsentModel(UserConsentEntity entity) {
         if (entity == null) {
             return null;
         }
 
-        GrantedConsentModel model = new GrantedConsentModel(entity.getClientId());
+        UserConsentModel model = new UserConsentModel(realm, entity.getClientId());
 
-        Collection<GrantedConsentRoleEntity> grantedRoleEntities = entity.getGrantedRoles();
+        Collection<UserConsentRoleEntity> grantedRoleEntities = entity.getGrantedRoles();
         if (grantedRoleEntities != null) {
-            for (GrantedConsentRoleEntity grantedRole : grantedRoleEntities) {
+            for (UserConsentRoleEntity grantedRole : grantedRoleEntities) {
                 model.addGrantedRole(grantedRole.getRoleId());
             }
         }
 
-        Collection<GrantedConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
+        Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
         if (grantedProtocolMapperEntities != null) {
-            for (GrantedConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
+            for (UserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
                 model.addGrantedProtocolMapper(grantedProtMapper.getProtocolMapperId());
             }
         }
@@ -588,14 +580,14 @@ public class UserAdapter implements UserModel {
     }
 
     // Update roles and protocolMappers to given consentEntity from the consentModel
-    private void updateGrantedConsentEntity(GrantedConsentEntity consentEntity, GrantedConsentModel consentModel) {
-        Collection<GrantedConsentProtocolMapperEntity> grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers();
-        Collection<GrantedConsentProtocolMapperEntity> mappersToRemove = new HashSet<GrantedConsentProtocolMapperEntity>(grantedProtocolMapperEntities);
+    private void updateGrantedConsentEntity(UserConsentEntity consentEntity, UserConsentModel consentModel) {
+        Collection<UserConsentProtocolMapperEntity> grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers();
+        Collection<UserConsentProtocolMapperEntity> mappersToRemove = new HashSet<UserConsentProtocolMapperEntity>(grantedProtocolMapperEntities);
 
-        for (String protocolMapperId : consentModel.getGrantedProtocolMappers()) {
-            GrantedConsentProtocolMapperEntity grantedProtocolMapperEntity = new GrantedConsentProtocolMapperEntity();
-            grantedProtocolMapperEntity.setGrantedConsent(consentEntity);
-            grantedProtocolMapperEntity.setProtocolMapperId(protocolMapperId);
+        for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) {
+            UserConsentProtocolMapperEntity grantedProtocolMapperEntity = new UserConsentProtocolMapperEntity();
+            grantedProtocolMapperEntity.setUserConsent(consentEntity);
+            grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId());
 
             // Check if it's already there
             if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) {
@@ -607,17 +599,17 @@ public class UserAdapter implements UserModel {
             }
         }
         // Those mappers were no longer on consentModel and will be removed
-        for (GrantedConsentProtocolMapperEntity toRemove : mappersToRemove) {
+        for (UserConsentProtocolMapperEntity toRemove : mappersToRemove) {
             grantedProtocolMapperEntities.remove(toRemove);
             em.remove(toRemove);
         }
 
-        Collection<GrantedConsentRoleEntity> grantedRoleEntities = consentEntity.getGrantedRoles();
-        Set<GrantedConsentRoleEntity> rolesToRemove = new HashSet<GrantedConsentRoleEntity>(grantedRoleEntities);
-        for (String roleId : consentModel.getGrantedRoles()) {
-            GrantedConsentRoleEntity consentRoleEntity = new GrantedConsentRoleEntity();
-            consentRoleEntity.setGrantedConsent(consentEntity);
-            consentRoleEntity.setRoleId(roleId);
+        Collection<UserConsentRoleEntity> grantedRoleEntities = consentEntity.getGrantedRoles();
+        Set<UserConsentRoleEntity> rolesToRemove = new HashSet<UserConsentRoleEntity>(grantedRoleEntities);
+        for (RoleModel role : consentModel.getGrantedRoles()) {
+            UserConsentRoleEntity consentRoleEntity = new UserConsentRoleEntity();
+            consentRoleEntity.setUserConsent(consentEntity);
+            consentRoleEntity.setRoleId(role.getId());
 
             // Check if it's already there
             if (!grantedRoleEntities.contains(consentRoleEntity)) {
@@ -629,7 +621,7 @@ public class UserAdapter implements UserModel {
             }
         }
         // Those roles were no longer on consentModel and will be removed
-        for (GrantedConsentRoleEntity toRemove : rolesToRemove) {
+        for (UserConsentRoleEntity toRemove : rolesToRemove) {
             grantedRoleEntities.remove(toRemove);
             em.remove(toRemove);
         }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index 8e07bc9..2cb2c79 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -305,7 +305,8 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
             throw new RuntimeException("protocol mapper name must be unique per protocol");
         }
         ProtocolMapperEntity entity = new ProtocolMapperEntity();
-        entity.setId(KeycloakModelUtils.generateId());
+        String id = model.getId() != null ? model.getId() : KeycloakModelUtils.generateId();
+        entity.setId(id);
         entity.setProtocol(model.getProtocol());
         entity.setName(model.getName());
         entity.setProtocolMapper(model.getProtocolMapper());
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index f71c797..83bc85c 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -17,6 +17,7 @@ import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserProvider;
 import org.keycloak.models.entities.FederatedIdentityEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
 import org.keycloak.models.utils.CredentialValidation;
 
@@ -43,7 +44,6 @@ public class MongoUserProvider implements UserProvider {
 
     @Override
     public void close() {
-        // TODO
     }
 
     @Override
@@ -274,11 +274,7 @@ public class MongoUserProvider implements UserProvider {
 
     @Override
     public boolean removeUser(RealmModel realm, UserModel user) {
-        DBObject query = new QueryBuilder()
-                .and("_id").is(user.getId())
-                .and("realmId").is(realm.getId())
-                .get();
-        return getMongoStore().removeEntities(MongoUserEntity.class, query, invocationContext);
+        return getMongoStore().removeEntity(MongoUserEntity.class, user.getId(), invocationContext);
     }
 
 
@@ -339,32 +335,60 @@ public class MongoUserProvider implements UserProvider {
         DBObject query = new QueryBuilder()
                 .and("realmId").is(realm.getId())
                 .get();
-        getMongoStore().removeEntities(MongoUserEntity.class, query, invocationContext);
+        getMongoStore().removeEntities(MongoUserEntity.class, query, true, invocationContext);
     }
 
     @Override
     public void preRemove(RealmModel realm, UserFederationProviderModel link) {
+        // Remove all users linked with federationProvider and their consents
         DBObject query = new QueryBuilder()
                 .and("realmId").is(realm.getId())
                 .and("federationLink").is(link.getId())
                 .get();
-        getMongoStore().removeEntities(MongoUserEntity.class, query, invocationContext);
+        getMongoStore().removeEntities(MongoUserEntity.class, query, true, invocationContext);
 
     }
 
     @Override
     public void preRemove(RealmModel realm, ClientModel client) {
-        // TODO
+        // Remove all role mappings and consents mapped to all roles of this client
+        for (RoleModel role : client.getRoles()) {
+            preRemove(realm, role);
+        }
+
+        // Finally remove all consents of this client
+        DBObject query = new QueryBuilder()
+                .and("clientId").is(client.getId())
+                .get();
+        getMongoStore().removeEntities(MongoUserConsentEntity.class, query, false, invocationContext);
     }
 
     @Override
     public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
-        // TODO
+        // Remove this protocol mapper from all consents, which has it
+        DBObject query = new QueryBuilder()
+                .and("grantedProtocolMappers").is(protocolMapper.getId())
+                .get();
+        DBObject pull = new BasicDBObject("$pull", query);
+        getMongoStore().updateEntities(MongoUserEntity.class, query, pull, invocationContext);
     }
 
     @Override
     public void preRemove(RealmModel realm, RoleModel role) {
-        // todo not sure what to do for this
+        // Remove this role from all users, which has it
+        DBObject query = new QueryBuilder()
+                .and("roleIds").is(role.getId())
+                .get();
+
+        DBObject pull = new BasicDBObject("$pull", query);
+        getMongoStore().updateEntities(MongoUserEntity.class, query, pull, invocationContext);
+
+        // Remove this role from all consents, which has it
+        query = new QueryBuilder()
+                .and("grantedRoles").is(role.getId())
+                .get();
+        pull = new BasicDBObject("$pull", query);
+        getMongoStore().updateEntities(MongoUserConsentEntity.class, query, pull, invocationContext);
     }
 
     @Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 5fa3e52..b054793 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -2,10 +2,15 @@ package org.keycloak.models.mongo.keycloak.adapters;
 
 import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
 
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.ClientModel;
-import org.keycloak.models.GrantedConsentModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
@@ -13,7 +18,9 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.entities.CredentialEntity;
+import org.keycloak.models.entities.UserConsentEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
+import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
 import org.keycloak.models.mongo.utils.MongoModelUtils;
 import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
@@ -22,9 +29,9 @@ import org.keycloak.util.Time;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -360,30 +367,18 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
 
     @Override
     public Set<RoleModel> getRoleMappings() {
-        Set<RoleModel> result = new HashSet<RoleModel>();
-        List<MongoRoleEntity> roles = MongoModelUtils.getAllRolesOfUser(this, invocationContext);
-
-        for (MongoRoleEntity role : roles) {
-            if (realm.getId().equals(role.getRealmId())) {
-                result.add(new RoleAdapter(session, realm, role, realm, invocationContext));
-            } else {
-                // Likely applicationRole, but we don't have this application yet
-                result.add(new RoleAdapter(session, realm, role, invocationContext));
-            }
-        }
-        return result;
+        List<RoleModel> roles = MongoModelUtils.getAllRolesOfUser(realm, this);
+        return new HashSet<RoleModel>(roles);
     }
 
     @Override
     public Set<RoleModel> getRealmRoleMappings() {
         Set<RoleModel> allRoles = getRoleMappings();
 
-        // Filter to retrieve just realm roles TODO: Maybe improve to avoid filter programmatically... Maybe have separate fields for realmRoles and appRoles on user?
+        // Filter to retrieve just realm roles
         Set<RoleModel> realmRoles = new HashSet<RoleModel>();
         for (RoleModel role : allRoles) {
-            MongoRoleEntity roleEntity = ((RoleAdapter) role).getRole();
-
-            if (realm.getId().equals(roleEntity.getRealmId())) {
+            if (role.getContainer() instanceof RealmModel) {
                 realmRoles.add(role);
             }
         }
@@ -400,11 +395,11 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     @Override
     public Set<RoleModel> getClientRoleMappings(ClientModel app) {
         Set<RoleModel> result = new HashSet<RoleModel>();
-        List<MongoRoleEntity> roles = MongoModelUtils.getAllRolesOfUser(this, invocationContext);
+        List<RoleModel> roles = MongoModelUtils.getAllRolesOfUser(realm, this);
 
-        for (MongoRoleEntity role : roles) {
-            if (app.getId().equals(role.getClientId())) {
-                result.add(new RoleAdapter(session, realm, role, app, invocationContext));
+        for (RoleModel role : roles) {
+            if (app.equals(role.getContainer())) {
+                result.add(role);
             }
         }
         return result;
@@ -422,32 +417,96 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     }
 
     @Override
-    public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
-        // TODO
-        return null;
+    public void addConsent(UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
+        if (getConsentEntityByClientId(clientId) != null) {
+            throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
+        }
+
+        MongoUserConsentEntity consentEntity = new MongoUserConsentEntity();
+        consentEntity.setUserId(getId());
+        consentEntity.setClientId(clientId);
+        fillEntityFromModel(consent, consentEntity);
+        getMongoStore().insertEntity(consentEntity, invocationContext);
     }
 
     @Override
-    public GrantedConsentModel getGrantedConsentByClient(String clientId) {
-        // TODO
-        return null;
+    public UserConsentModel getConsentByClient(String clientId) {
+        UserConsentEntity consentEntity = getConsentEntityByClientId(clientId);
+        return consentEntity!=null ? toConsentModel(consentEntity) : null;
     }
 
     @Override
-    public List<GrantedConsentModel> getGrantedConsents() {
-        // TODO
-        return null;
+    public List<UserConsentModel> getConsents() {
+        List<UserConsentModel> result = new ArrayList<UserConsentModel>();
+
+        DBObject query = new QueryBuilder()
+                .and("userId").is(getId())
+                .get();
+        List<MongoUserConsentEntity> grantedConsents = getMongoStore().loadEntities(MongoUserConsentEntity.class, query, invocationContext);
+
+        for (UserConsentEntity consentEntity : grantedConsents) {
+            UserConsentModel model = toConsentModel(consentEntity);
+            result.add(model);
+        }
+
+        return result;
+    }
+
+    private MongoUserConsentEntity getConsentEntityByClientId(String clientId) {
+        DBObject query = new QueryBuilder()
+                .and("userId").is(getId())
+                .and("clientId").is(clientId)
+                .get();
+        return getMongoStore().loadSingleEntity(MongoUserConsentEntity.class, query, invocationContext);
+    }
+
+    private UserConsentModel toConsentModel(UserConsentEntity entity) {
+        UserConsentModel model = new UserConsentModel(realm, entity.getClientId());
+        for (String roleId : entity.getGrantedRoles()) {
+            model.addGrantedRole(roleId);
+        }
+        for (String protMapperId : entity.getGrantedProtocolMappers()) {
+            model.addGrantedProtocolMapper(protMapperId);
+        }
+        return model;
+    }
+
+    // Fill roles and protocolMappers to entity
+    private void fillEntityFromModel(UserConsentModel consent, MongoUserConsentEntity consentEntity) {
+        List<String> roleIds = new LinkedList<String>();
+        for (RoleModel role : consent.getGrantedRoles()) {
+            roleIds.add(role.getId());
+        }
+        consentEntity.setGrantedRoles(roleIds);
+
+        List<String> protMapperIds = new LinkedList<String>();
+        for (ProtocolMapperModel protMapperModel : consent.getGrantedProtocolMappers()) {
+            protMapperIds.add(protMapperModel.getId());
+        }
+        consentEntity.setGrantedProtocolMappers(protMapperIds);
     }
 
     @Override
-    public void updateGrantedConsent(GrantedConsentModel consent) {
-        // TODO
+    public void updateConsent(UserConsentModel consent) {
+        String clientId = consent.getClient().getId();
+        MongoUserConsentEntity consentEntity = getConsentEntityByClientId(clientId);
+        if (consentEntity == null) {
+            throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
+        } else {
+            fillEntityFromModel(consent, consentEntity);
+            getMongoStore().updateEntity(consentEntity, invocationContext);
+        }
     }
 
     @Override
-    public boolean revokeGrantedConsentForClient(String clientId) {
-        // TODO
-        return false;
+    public boolean revokeConsentForClient(String clientId) {
+        MongoUserConsentEntity entity = getConsentEntityByClientId(clientId);
+        if (entity == null) {
+            return false;
+        }
+
+        return getMongoStore().removeEntity(entity, invocationContext);
     }
 
     @Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientEntity.java
index cc97d5d..8c12715 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoClientEntity.java
@@ -19,6 +19,6 @@ public class MongoClientEntity extends ClientEntity implements MongoIdentifiable
         DBObject query = new QueryBuilder()
                 .and("clientId").is(getId())
                 .get();
-        context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context);
+        context.getMongoStore().removeEntities(MongoRoleEntity.class, query, true, context);
     }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
index 2209751..a64c2b5 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java
@@ -19,13 +19,10 @@ public class MongoRealmEntity extends RealmEntity implements MongoIdentifiableEn
                 .and("realmId").is(getId())
                 .get();
 
-        // Remove all users of this realm
-        context.getMongoStore().removeEntities(MongoUserEntity.class, query, context);
-
         // Remove all roles of this realm
-        context.getMongoStore().removeEntities(MongoRoleEntity.class, query, context);
+        context.getMongoStore().removeEntities(MongoRoleEntity.class, query, true, context);
 
-        // Remove all applications of this realm
-        context.getMongoStore().removeEntities(MongoClientEntity.class, query, context);
+        // Remove all clients of this realm
+        context.getMongoStore().removeEntities(MongoClientEntity.class, query, true, context);
     }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java
index dc820c2..2021069 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java
@@ -41,26 +41,15 @@ public class MongoRoleEntity extends RoleEntity implements MongoIdentifiableEnti
     public void afterRemove(MongoStoreInvocationContext invContext) {
         MongoStore mongoStore = invContext.getMongoStore();
 
-        // Remove this role from all users, which has it
+        // Remove this scope from all clients, which has it
         DBObject query = new QueryBuilder()
-                .and("roleIds").is(getId())
-                .get();
-
-        List<MongoUserEntity> users = mongoStore.loadEntities(MongoUserEntity.class, query, invContext);
-        for (MongoUserEntity user : users) {
-            //logger.info("Removing role " + getName() + " from user " + user.getUsername());
-            mongoStore.pullItemFromList(user, "roleIds", getId(), invContext);
-        }
-
-        // Remove this scope from all users, which has it
-        query = new QueryBuilder()
                 .and("scopeIds").is(getId())
                 .get();
 
-        users = mongoStore.loadEntities(MongoUserEntity.class, query, invContext);
-        for (MongoUserEntity user : users) {
+        List<MongoClientEntity> clients = mongoStore.loadEntities(MongoClientEntity.class, query, invContext);
+        for (MongoClientEntity client : clients) {
             //logger.info("Removing scope " + getName() + " from user " + user.getUsername());
-            mongoStore.pullItemFromList(user, "scopeIds", getId(), invContext);
+            mongoStore.pullItemFromList(client, "scopeIds", getId(), invContext);
         }
 
         // Remove defaultRoles from realm
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserConsentEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserConsentEntity.java
new file mode 100644
index 0000000..16e0026
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserConsentEntity.java
@@ -0,0 +1,17 @@
+package org.keycloak.models.mongo.keycloak.entities;
+
+import org.keycloak.connections.mongo.api.MongoCollection;
+import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.entities.UserConsentEntity;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@MongoCollection(collectionName = "userConsents")
+public class MongoUserConsentEntity extends UserConsentEntity implements MongoIdentifiableEntity {
+
+    @Override
+    public void afterRemove(MongoStoreInvocationContext invocationContext) {
+    }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java
index c9f317e..d1700d8 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java
@@ -1,5 +1,7 @@
 package org.keycloak.models.mongo.keycloak.entities;
 
+import com.mongodb.DBObject;
+import com.mongodb.QueryBuilder;
 import org.keycloak.connections.mongo.api.MongoCollection;
 import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
@@ -19,6 +21,12 @@ public class MongoUserEntity extends UserEntity implements MongoIdentifiableEnti
     }
 
     @Override
-    public void afterRemove(MongoStoreInvocationContext invocationContext) {
+    public void afterRemove(MongoStoreInvocationContext context) {
+        // Remove all consents of this user
+        DBObject query = new QueryBuilder()
+                .and("userId").is(getId())
+                .get();
+
+        context.getMongoStore().removeEntities(MongoUserConsentEntity.class, query, true, context);
     }
 }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java
index 48828de..02923cf 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/utils/MongoModelUtils.java
@@ -4,6 +4,8 @@ import com.mongodb.DBObject;
 import com.mongodb.QueryBuilder;
 import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.entities.ClientEntity;
 import org.keycloak.models.mongo.keycloak.adapters.ClientAdapter;
@@ -12,6 +14,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
 import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
 
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 
 /**
@@ -20,18 +23,22 @@ import java.util.List;
 public class MongoModelUtils {
 
     // Get everything including both application and realm roles
-    public static List<MongoRoleEntity> getAllRolesOfUser(UserModel user, MongoStoreInvocationContext invContext) {
+    public static List<RoleModel> getAllRolesOfUser(RealmModel realm, UserModel user) {
         MongoUserEntity userEntity = ((UserAdapter)user).getUser();
         List<String> roleIds = userEntity.getRoleIds();
 
         if (roleIds == null || roleIds.isEmpty()) {
-            return Collections.EMPTY_LIST;
+            return Collections.emptyList();
         }
 
-        DBObject query = new QueryBuilder()
-                .and("_id").in(roleIds)
-                .get();
-        return invContext.getMongoStore().loadEntities(MongoRoleEntity.class, query, invContext);
+        List<RoleModel> roles = new LinkedList<RoleModel>();
+        for (String roleId : roleIds) {
+            RoleModel role = realm.getRoleById(roleId);
+            if (role != null) {
+                roles.add(role);
+            }
+        }
+        return roles;
     }
 
     // Get everything including both application and realm scopes
@@ -40,7 +47,7 @@ public class MongoModelUtils {
         List<String> scopeIds = scopedEntity.getScopeIds();
 
         if (scopeIds == null || scopeIds.isEmpty()) {
-            return Collections.EMPTY_LIST;
+            return Collections.emptyList();
         }
 
         DBObject query = new QueryBuilder()
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java
index 5b7e8d3..0c637db 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java
@@ -121,7 +121,7 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
         DBObject query = new QueryBuilder()
                 .and("sessionId").is(getId())
                 .get();
-        context.getMongoStore().removeEntities(MongoClientSessionEntity.class, query, context);
+        context.getMongoStore().removeEntities(MongoClientSessionEntity.class, query, true, context);
     }
 
     public Map<String, String> getNotes() {
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
index 0bc5849..848fdeb 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
@@ -194,18 +194,14 @@ public class MongoUserSessionProvider implements UserSessionProvider {
     @Override
     public void removeUserSessions(RealmModel realm, UserModel user) {
         DBObject query = new BasicDBObject("user", user.getId());
-        mongoStore.removeEntities(MongoUserSessionEntity.class, query, invocationContext);
+        mongoStore.removeEntities(MongoUserSessionEntity.class, query, true, invocationContext);
     }
 
     @Override
     public void removeUserSessions(RealmModel realm) {
         DBObject query = new BasicDBObject("realmId", realm.getId());
-        mongoStore.removeEntities(MongoUserSessionEntity.class, query, invocationContext);
-        query = new QueryBuilder()
-                .and("realmId").is(realm.getId())
-                .get();
-
-        mongoStore.removeEntities(MongoClientSessionEntity.class, query, invocationContext);
+        mongoStore.removeEntities(MongoUserSessionEntity.class, query, false, invocationContext);
+        mongoStore.removeEntities(MongoClientSessionEntity.class, query, false, invocationContext);
     }
 
     @Override
@@ -216,20 +212,20 @@ public class MongoUserSessionProvider implements UserSessionProvider {
                 .and("started").lessThan(currentTime - realm.getSsoSessionMaxLifespan())
                 .get();
 
-        mongoStore.removeEntities(MongoUserSessionEntity.class, query, invocationContext);
+        mongoStore.removeEntities(MongoUserSessionEntity.class, query, true, invocationContext);
         query = new QueryBuilder()
                 .and("realmId").is(realm.getId())
                 .and("lastSessionRefresh").lessThan(currentTime - realm.getSsoSessionIdleTimeout())
                 .get();
 
-        mongoStore.removeEntities(MongoUserSessionEntity.class, query, invocationContext);
+        mongoStore.removeEntities(MongoUserSessionEntity.class, query, true, invocationContext);
         query = new QueryBuilder()
                 .and("sessionId").is(null)
                 .and("realmId").is(realm.getId())
                 .and("timestamp").lessThan(currentTime - RealmInfoUtil.getDettachedClientSessionLifespan(realm))
                 .get();
 
-        mongoStore.removeEntities(MongoClientSessionEntity.class, query, invocationContext);
+        mongoStore.removeEntities(MongoClientSessionEntity.class, query, false, invocationContext);
     }
 
     @Override
@@ -291,7 +287,7 @@ public class MongoUserSessionProvider implements UserSessionProvider {
                 .or(new BasicDBObject("username", user.getUsername()), new BasicDBObject("username", user.getEmail()))
                 .and("realmId").is(realm.getId())
                 .get();
-        mongoStore.removeEntities(MongoUsernameLoginFailureEntity.class, query, invocationContext);
+        mongoStore.removeEntities(MongoUsernameLoginFailureEntity.class, query, true, invocationContext);
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 4814f30..7647b36 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -14,7 +14,7 @@ import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.GrantedConsentModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
@@ -420,14 +420,14 @@ public class AuthenticationManager {
         if (client.isConsentRequired()) {
             accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
 
-            GrantedConsentModel grantedConsent = user.getGrantedConsentByClient(client.getId());
+            UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
 
             List<RoleModel> realmRoles = new LinkedList<RoleModel>();
             MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
             for (RoleModel r : accessCode.getRequestedRoles()) {
 
                 // Consent already granted by user
-                if (grantedConsent != null && grantedConsent.getGrantedRoles().contains(r.getId())) {
+                if (grantedConsent != null && grantedConsent.isRoleGranted(r)) {
                     continue;
                 }
 
@@ -439,10 +439,10 @@ public class AuthenticationManager {
             }
 
             List<ProtocolMapperModel> protocolMappers = new LinkedList<ProtocolMapperModel>();
-            for (ProtocolMapperModel model : client.getProtocolMappers()) {
-                if (model.isConsentRequired() && model.getProtocol().equals(clientSession.getAuthMethod()) && model.getConsentText() != null) {
-                    if (grantedConsent == null || !grantedConsent.getGrantedProtocolMappers().contains(model.getId())) {
-                        protocolMappers.add(model);
+            for (ProtocolMapperModel protocolMapper : client.getProtocolMappers()) {
+                if (protocolMapper.isConsentRequired() && protocolMapper.getProtocol().equals(clientSession.getAuthMethod()) && protocolMapper.getConsentText() != null) {
+                    if (grantedConsent == null || !grantedConsent.isProtocolMapperGranted(protocolMapper)) {
+                        protocolMappers.add(protocolMapper);
                     }
                 }
             }
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index af1c872..3a775e1 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -76,7 +76,6 @@ import javax.ws.rs.core.Variant;
 
 import java.lang.reflect.Method;
 import java.net.URI;
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -512,7 +511,7 @@ public class AccountService {
 
         // Revoke grant in UserModel
         UserModel user = auth.getUser();
-        user.revokeGrantedConsentForClient(client.getId());
+        user.revokeConsentForClient(client.getId());
 
         // Logout clientSessions for this user and client
         List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index a0eb4dc..d4aa8d7 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -34,7 +34,7 @@ import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.GrantedConsentModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.ProtocolMapperModel;
@@ -609,9 +609,10 @@ public class LoginActionsService {
             return protocol.consentDenied(clientSession);
         }
 
-        GrantedConsentModel grantedConsent = user.getGrantedConsentByClient(client.getId());
+        UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
         if (grantedConsent == null) {
-            grantedConsent = user.addGrantedConsent(new GrantedConsentModel(client.getId()));
+            grantedConsent = new UserConsentModel(realm, client.getId());
+            user.addConsent(grantedConsent);
         }
         for (String roleId : clientSession.getRoles()) {
             grantedConsent.addGrantedRole(roleId);
@@ -622,7 +623,7 @@ public class LoginActionsService {
                 grantedConsent.addGrantedProtocolMapper(protocolMapper.getId());
             }
         }
-        user.updateGrantedConsent(grantedConsent);
+        user.updateConsent(grantedConsent);
 
         event.success();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
index 0e48914..f7f48b3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
@@ -258,6 +258,7 @@ public class AdapterTest extends AbstractModelTest {
         commit();
         realmModel = model.getRealm("JUGGLER");
         app = realmModel.getClientByClientId("test-app");
+        user = realmManager.getSession().users().getUserByUsername("bburke", realmModel);
 
         Assert.assertTrue(realmModel.removeRoleById(realmRole.getId()));
         Assert.assertFalse(realmModel.removeRoleById(realmRole.getId()));
@@ -266,6 +267,9 @@ public class AdapterTest extends AbstractModelTest {
         Assert.assertTrue(realmModel.removeRoleById(appRole.getId()));
         Assert.assertFalse(realmModel.removeRoleById(appRole.getId()));
         assertNull(app.getRole(appRole.getName()));
+
+        user = realmManager.getSession().users().getUserByUsername("bburke", realmModel);
+
     }
 
     @Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java
index f0fc63b..e56d462 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java
@@ -9,6 +9,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 import org.keycloak.services.managers.ClientManager;
 
 import java.util.Iterator;
@@ -63,6 +64,9 @@ public class ClientModelTest extends AbstractModelTest {
     public void json() {
         ClientRepresentation representation = ModelToRepresentation.toRepresentation(client);
         representation.setId(null);
+        for (ProtocolMapperRepresentation protocolMapper : representation.getProtocolMappers()) {
+            protocolMapper.setId(null);
+        }
 
         RealmModel realm = realmManager.createRealm("copy");
         ClientModel copy = RepresentationToModel.createClient(session, realm, representation, true);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index 54f4cbe..5bdc191 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -14,6 +14,7 @@ import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
@@ -238,6 +239,23 @@ public class ImportTest extends AbstractModelTest {
         String includeInIdToken = gssCredentialMapper.getConfig().get(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
         Assert.assertTrue(includeInAccessToken.equalsIgnoreCase("true"));
         Assert.assertTrue(includeInIdToken == null || Boolean.parseBoolean(includeInIdToken) == false);
+
+        // Test user consents
+        admin =  session.users().getUserByUsername("admin", realm);
+        Assert.assertEquals(2, admin.getConsents().size());
+
+        UserConsentModel appAdminConsent = admin.getConsentByClient(application.getId());
+        Assert.assertEquals(2, appAdminConsent.getGrantedRoles().size());
+        Assert.assertTrue(appAdminConsent.getGrantedProtocolMappers() == null || appAdminConsent.getGrantedProtocolMappers().isEmpty());
+        Assert.assertTrue(appAdminConsent.isRoleGranted(realm.getRole("admin")));
+        Assert.assertTrue(appAdminConsent.isRoleGranted(application.getRole("app-admin")));
+
+        UserConsentModel otherAppAdminConsent = admin.getConsentByClient(otherApp.getId());
+        Assert.assertEquals(1, otherAppAdminConsent.getGrantedRoles().size());
+        Assert.assertEquals(1, otherAppAdminConsent.getGrantedProtocolMappers().size());
+        Assert.assertTrue(otherAppAdminConsent.isRoleGranted(realm.getRole("admin")));
+        Assert.assertFalse(otherAppAdminConsent.isRoleGranted(application.getRole("app-admin")));
+        Assert.assertTrue(otherAppAdminConsent.isProtocolMapperGranted(gssCredentialMapper));
     }
 
     @Test
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index 4044399..1c0b7e6 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -74,6 +74,15 @@
             "applicationRoles": {
                 "Application": [ "app-admin" ],
                 "OtherApp": [  "otherapp-admin" ]
+            },
+            "clientConsents": {
+                "Application": {
+                    "grantedRoles": [ "456", "789" ]
+                },
+                "OtherApp": {
+                    "grantedProtocolMappers": [ "123" ],
+                    "grantedRoles": [ "456" ]
+                }
             }
         },
         {
@@ -113,6 +122,7 @@
             "enabled": true,
             "protocolMappers" : [
                 {
+                    "id": "123",
                     "name" : "gss delegation credential",
                     "protocol" : "openid-connect",
                     "protocolMapper" : "oidc-usersessionmodel-note-mapper",
@@ -138,12 +148,14 @@
     "roles" : {
         "realm" : [
             {
+                "id":   "456",
                 "name": "admin"
             }
         ],
         "application" : {
             "Application" : [
                 {
+                    "id":   "789",
                     "name": "app-admin"
                 },
                 {