keycloak-uncached

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index de4bf38..cb6aa32 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -264,13 +264,22 @@ public class RealmRepresentation {
         return scopeMappings;
     }
 
-    public ScopeMappingRepresentation scopeMapping(String username) {
+    public ScopeMappingRepresentation clientScopeMapping(String clientName) {
         ScopeMappingRepresentation mapping = new ScopeMappingRepresentation();
-        mapping.setClient(username);
+        mapping.setClient(clientName);
         if (scopeMappings == null) scopeMappings = new ArrayList<ScopeMappingRepresentation>();
         scopeMappings.add(mapping);
         return mapping;
     }
+
+    public ScopeMappingRepresentation clientTemplateScopeMapping(String clientTemplateName) {
+        ScopeMappingRepresentation mapping = new ScopeMappingRepresentation();
+        mapping.setClientTemplate(clientTemplateName);
+        if (scopeMappings == null) scopeMappings = new ArrayList<ScopeMappingRepresentation>();
+        scopeMappings.add(mapping);
+        return mapping;
+    }
+
     @Deprecated
     public Set<String> getRequiredCredentials() {
         return requiredCredentials;
diff --git a/core/src/main/java/org/keycloak/representations/idm/ScopeMappingRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ScopeMappingRepresentation.java
index 0300bd4..f8e2a7b 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ScopeMappingRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ScopeMappingRepresentation.java
@@ -27,6 +27,7 @@ import java.util.Set;
 public class ScopeMappingRepresentation {
     protected String self; // link
     protected String client;
+    protected String clientTemplate;
     protected Set<String> roles;
 
     public String getSelf() {
@@ -45,6 +46,14 @@ public class ScopeMappingRepresentation {
         this.client = client;
     }
 
+    public String getClientTemplate() {
+        return clientTemplate;
+    }
+
+    public void setClientTemplate(String clientTemplate) {
+        this.clientTemplate = clientTemplate;
+    }
+
     public Set<String> getRoles() {
         return roles;
     }
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index 98ac227..e0dc211 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -632,5 +632,15 @@ public final class KeycloakModelUtils {
         return false;
     }
 
+    public static ClientTemplateModel getClientTemplateByName(RealmModel realm, String templateName) {
+        for (ClientTemplateModel clientTemplate : realm.getClientTemplates()) {
+            if (templateName.equals(clientTemplate.getName())) {
+                return clientTemplate;
+            }
+        }
+
+        return null;
+    }
+
 
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index e6bb3c5..24e3ac8 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -42,6 +42,7 @@ import org.keycloak.models.ProtocolMapperModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredActionProviderModel;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.ScopeContainerModel;
 import org.keycloak.models.UserConsentModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
@@ -250,16 +251,14 @@ public class RepresentationToModel {
 
         if (rep.getScopeMappings() != null) {
             for (ScopeMappingRepresentation scope : rep.getScopeMappings()) {
-                ClientModel client = newRealm.getClientByClientId(scope.getClient());
-                if (client == null) {
-                    throw new RuntimeException("Unknown client specification in realm scope mappings");
-                }
+                ScopeContainerModel scopeContainer = getScopeContainerHavingScope(newRealm, scope);
+
                 for (String roleString : scope.getRoles()) {
                     RoleModel role = newRealm.getRole(roleString.trim());
                     if (role == null) {
                         role = newRealm.addRole(roleString.trim());
                     }
-                    client.addScopeMapping(role);
+                    scopeContainer.addScopeMapping(role);
                 }
 
             }
@@ -1205,20 +1204,36 @@ public class RepresentationToModel {
 
     public static void createClientScopeMappings(RealmModel realm, ClientModel clientModel, List<ScopeMappingRepresentation> mappings) {
         for (ScopeMappingRepresentation mapping : mappings) {
-            ClientModel client = realm.getClientByClientId(mapping.getClient());
-            if (client == null) {
-                throw new RuntimeException("Unknown client specified in client scope mappings");
-            }
+            ScopeContainerModel scopeContainer = getScopeContainerHavingScope(realm, mapping);
+
             for (String roleString : mapping.getRoles()) {
                 RoleModel role = clientModel.getRole(roleString.trim());
                 if (role == null) {
                     role = clientModel.addRole(roleString.trim());
                 }
-                client.addScopeMapping(role);
+                scopeContainer.addScopeMapping(role);
             }
         }
     }
 
+    private static ScopeContainerModel getScopeContainerHavingScope(RealmModel realm, ScopeMappingRepresentation scope) {
+        if (scope.getClient() != null) {
+            ClientModel client = realm.getClientByClientId(scope.getClient());
+            if (client == null) {
+                throw new RuntimeException("Unknown client specification in scope mappings: " + scope.getClient());
+            }
+            return client;
+        } else if (scope.getClientTemplate() != null) {
+            ClientTemplateModel clientTemplate = KeycloakModelUtils.getClientTemplateByName(realm, scope.getClientTemplate());
+            if (clientTemplate == null) {
+                throw new RuntimeException("Unknown clientTemplate specification in scope mappings: " + scope.getClientTemplate());
+            }
+            return clientTemplate;
+        } else {
+            throw new RuntimeException("Either client or clientTemplate needs to be specified in scope mappings");
+        }
+    }
+
     // Users
 
     public static UserModel createUser(KeycloakSession session, RealmModel newRealm, UserRepresentation userRep) {
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index 72cabb5..cf0c5ca 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -88,13 +88,14 @@ public class ExportUtils {
         List<ClientModel> allClients = new ArrayList<>(clients);
         Map<String, List<ScopeMappingRepresentation>> clientScopeReps = new HashMap<>();
 
+        // Scopes of clients
         for (ClientModel client : allClients) {
             Set<RoleModel> clientScopes = client.getScopeMappings();
             ScopeMappingRepresentation scopeMappingRep = null;
             for (RoleModel scope : clientScopes) {
                 if (scope.getContainer() instanceof RealmModel) {
                     if (scopeMappingRep == null) {
-                        scopeMappingRep = rep.scopeMapping(client.getClientId());
+                        scopeMappingRep = rep.clientScopeMapping(client.getClientId());
                     }
                     scopeMappingRep.role(scope.getName());
                 } else {
@@ -108,7 +109,7 @@ public class ExportUtils {
 
                     ScopeMappingRepresentation currentClientScope = null;
                     for (ScopeMappingRepresentation scopeMapping : currentAppScopes) {
-                        if (scopeMapping.getClient().equals(client.getClientId())) {
+                        if (client.getClientId().equals(scopeMapping.getClient())) {
                             currentClientScope = scopeMapping;
                             break;
                         }
@@ -123,6 +124,42 @@ public class ExportUtils {
             }
         }
 
+        // Scopes of client templates
+        for (ClientTemplateModel clientTemplate : realm.getClientTemplates()) {
+            Set<RoleModel> clientScopes = clientTemplate.getScopeMappings();
+            ScopeMappingRepresentation scopeMappingRep = null;
+            for (RoleModel scope : clientScopes) {
+                if (scope.getContainer() instanceof RealmModel) {
+                    if (scopeMappingRep == null) {
+                        scopeMappingRep = rep.clientTemplateScopeMapping(clientTemplate.getName());
+                    }
+                    scopeMappingRep.role(scope.getName());
+                } else {
+                    ClientModel app = (ClientModel)scope.getContainer();
+                    String appName = app.getClientId();
+                    List<ScopeMappingRepresentation> currentAppScopes = clientScopeReps.get(appName);
+                    if (currentAppScopes == null) {
+                        currentAppScopes = new ArrayList<>();
+                        clientScopeReps.put(appName, currentAppScopes);
+                    }
+
+                    ScopeMappingRepresentation currentClientTemplateScope = null;
+                    for (ScopeMappingRepresentation scopeMapping : currentAppScopes) {
+                        if (clientTemplate.getName().equals(scopeMapping.getClientTemplate())) {
+                            currentClientTemplateScope = scopeMapping;
+                            break;
+                        }
+                    }
+                    if (currentClientTemplateScope == null) {
+                        currentClientTemplateScope = new ScopeMappingRepresentation();
+                        currentClientTemplateScope.setClientTemplate(clientTemplate.getName());
+                        currentAppScopes.add(currentClientTemplateScope);
+                    }
+                    currentClientTemplateScope.role(scope.getName());
+                }
+            }
+        }
+
         if (clientScopeReps.size() > 0) {
             rep.setClientScopeMappings(clientScopeReps);
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java
index 2d1f54f..49580b1 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java
@@ -136,7 +136,7 @@ public class ClientTemplateResource {
     @NoCache
     public Response deleteClientTemplate() {
         auth.requireManage();
-        
+
         try {
             realm.removeClientTemplate(template.getId());
             adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CompositeRolesModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CompositeRolesModelTest.java
index 6a6c52d..038c148 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CompositeRolesModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CompositeRolesModelTest.java
@@ -58,7 +58,7 @@ public class CompositeRolesModelTest extends AbstractModelTest {
         RealmRepresentation rep = AbstractModelTest.loadJson("model/testrealm-noclient-id.json");
         rep.setId("TestNoClientID");
         expectedException.expect(RuntimeException.class);
-        expectedException.expectMessage("Unknown client specified in client scope mappings");
+        expectedException.expectMessage("Unknown client specification in scope mappings: some-client");
         manager.importRealm(rep);
 
     }
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 0f02666..136fc69 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
@@ -26,6 +26,7 @@ import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
 import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
 import org.keycloak.models.AuthenticationFlowModel;
 import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.FederatedIdentityModel;
 import org.keycloak.models.IdentityProviderModel;
@@ -321,13 +322,32 @@ public class ImportTest extends AbstractModelTest {
         Assert.assertEquals(1, otherApp.getProtocolMappers().size());
         Assert.assertNull(otherApp.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "username"));
         ProtocolMapperModel gssCredentialMapper = otherApp.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME);
-        Assert.assertEquals(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, gssCredentialMapper.getName());
-        Assert.assertEquals( OIDCLoginProtocol.LOGIN_PROTOCOL, gssCredentialMapper.getProtocol());
-        Assert.assertEquals(UserSessionNoteMapper.PROVIDER_ID, gssCredentialMapper.getProtocolMapper());
-        String includeInAccessToken = gssCredentialMapper.getConfig().get(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
-        String includeInIdToken = gssCredentialMapper.getConfig().get(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
-        Assert.assertTrue(includeInAccessToken.equalsIgnoreCase("true"));
-        Assert.assertTrue(includeInIdToken == null || Boolean.parseBoolean(includeInIdToken) == false);
+        assertGssProtocolMapper(gssCredentialMapper);
+
+        // Test clientTemplates
+        List<ClientTemplateModel> clientTemplates = realm.getClientTemplates();
+        Assert.assertEquals(1, clientTemplates.size());
+        ClientTemplateModel clientTemplate = clientTemplates.get(0);
+        Assert.assertEquals("foo-template", clientTemplate.getName());
+        Assert.assertEquals("foo-template-desc", clientTemplate.getDescription());
+        Assert.assertEquals(OIDCLoginProtocol.LOGIN_PROTOCOL, clientTemplate.getProtocol());
+        Assert.assertEquals(1, clientTemplate.getProtocolMappers().size());
+        ProtocolMapperModel templateGssCredentialMapper = clientTemplate.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME);
+        assertGssProtocolMapper(templateGssCredentialMapper);
+
+        // Test client template scopes
+        Set<RoleModel> allClientTemplateScopes = clientTemplate.getScopeMappings();
+        Assert.assertEquals(3, allClientTemplateScopes.size());
+        Assert.assertTrue(allClientTemplateScopes.contains(realm.getRole("admin")));
+        Assert.assertTrue(allClientTemplateScopes.contains(application.getRole("app-user")));
+        Assert.assertTrue(allClientTemplateScopes.contains(application.getRole("app-admin")));
+
+        Set<RoleModel> clientTemplateRealmScopes = clientTemplate.getRealmScopeMappings();
+        Assert.assertTrue(clientTemplateRealmScopes.contains(realm.getRole("admin")));
+
+        Set<RoleModel> clientTemplateAppScopes = KeycloakModelUtils.getClientScopeMappings(application, clientTemplate);//application.getClientScopeMappings(oauthClient);
+        Assert.assertTrue(clientTemplateAppScopes.contains(application.getRole("app-user")));
+        Assert.assertTrue(clientTemplateAppScopes.contains(application.getRole("app-admin")));
 
         // Test user consents
         admin =  session.users().getUserByUsername("admin", realm);
@@ -380,4 +400,14 @@ public class ImportTest extends AbstractModelTest {
         Assert.assertEquals(expectedType, requiredCreds.get(0).getType());
     }
 
+    private static void assertGssProtocolMapper(ProtocolMapperModel gssCredentialMapper) {
+        Assert.assertEquals(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, gssCredentialMapper.getName());
+        Assert.assertEquals( OIDCLoginProtocol.LOGIN_PROTOCOL, gssCredentialMapper.getProtocol());
+        Assert.assertEquals(UserSessionNoteMapper.PROVIDER_ID, gssCredentialMapper.getProtocolMapper());
+        String includeInAccessToken = gssCredentialMapper.getConfig().get(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
+        String includeInIdToken = gssCredentialMapper.getConfig().get(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
+        Assert.assertTrue(includeInAccessToken.equalsIgnoreCase("true"));
+        Assert.assertTrue(includeInIdToken == null || Boolean.parseBoolean(includeInIdToken) == false);
+    }
+
 }
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index a08f486..e2c07c0 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -195,6 +195,28 @@
             "secret": "clientpassword"
         }
     ],
+    "clientTemplates" : [
+        {
+            "name" : "foo-template",
+            "description" : "foo-template-desc",
+            "protocol" : "openid-connect",
+            "protocolMappers" : [
+                {
+                    "name" : "gss delegation credential",
+                    "protocol" : "openid-connect",
+                    "protocolMapper" : "oidc-usersessionmodel-note-mapper",
+                    "consentRequired" : true,
+                    "consentText" : "gss delegation credential",
+                    "config" : {
+                        "user.session.note" : "gss_delegation_credential",
+                        "access.token.claim" : "true",
+                        "claim.name" : "gss_delegation_credential",
+                        "Claim JSON Type" : "String"
+                    }
+                }
+            ]
+        }
+    ],
     "roles" : {
         "realm" : [
             {
@@ -226,6 +248,10 @@
         {
             "client": "oauthclient",
             "roles": ["admin"]
+        },
+        {
+            "clientTemplate": "foo-template",
+            "roles": ["admin"]
         }
     ],
     "applicationScopeMappings": {
@@ -233,6 +259,10 @@
             {
                 "client": "oauthclient",
                 "roles": ["app-user"]
+            },
+            {
+                "clientTemplate": "foo-template",
+                "roles": ["app-user", "app-admin" ]
             }
         ]