From d939b6a431dc0ccd0fbbc610b4525f86fb7f323e Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 18 Dec 2015 17:15:27 -0500 Subject: [PATCH] template scope --- .../META-INF/jpa-changelog-1.8.0.xml | 25 ++- .../main/resources/META-INF/persistence.xml | 1 + .../idm/ClientRepresentation.java | 29 ++++ .../idm/ClientTemplateRepresentation.java | 9 + .../theme/base/admin/resources/js/app.js | 18 ++ .../admin/resources/js/controllers/clients.js | 128 +++++++++++++- .../theme/base/admin/resources/js/services.js | 46 +++++ .../resources/partials/client-mappers.html | 15 +- .../partials/client-scope-mappings.html | 26 ++- .../client-template-scope-mappings.html | 117 +++++++++++++ .../templates/kc-tabs-client-template.html | 4 + .../resource/ClientTemplateResource.java | 5 + .../java/org/keycloak/models/ClientModel.java | 18 +- .../keycloak/models/ClientTemplateModel.java | 2 +- .../keycloak/models/ScopeContainerModel.java | 24 +++ .../org/keycloak/models/ScopeMapperModel.java | 9 - .../models/entities/ClientEntity.java | 27 +++ .../models/entities/ClientTemplateEntity.java | 18 ++ .../models/utils/KeycloakModelUtils.java | 19 +++ .../models/utils/ModelToRepresentation.java | 4 + .../models/utils/RepresentationToModel.java | 39 ++++- .../cache/infinispan/ClientAdapter.java | 60 ++++--- .../infinispan/ClientTemplateAdapter.java | 64 +++++++ .../models/cache/entities/CachedClient.java | 18 ++ .../cache/entities/CachedClientTemplate.java | 14 ++ .../keycloak/models/jpa/ClientAdapter.java | 61 ++++--- .../models/jpa/ClientTemplateAdapter.java | 85 ++++++++++ .../keycloak/models/jpa/JpaRealmProvider.java | 3 + .../org/keycloak/models/jpa/RealmAdapter.java | 4 + .../models/jpa/entities/ClientEntity.java | 33 ++++ .../jpa/entities/ClientTemplateEntity.java | 10 ++ .../entities/TemplateScopeMappingEntity.java | 99 +++++++++++ .../keycloak/adapters/ClientAdapter.java | 51 ++++-- .../adapters/ClientTemplateAdapter.java | 63 +++++++ .../models/mongo/utils/MongoModelUtils.java | 16 ++ .../keycloak/protocol/oidc/TokenManager.java | 19 ++- .../admin/ClientTemplateResource.java | 24 ++- .../admin/ScopeMappedClientResource.java | 22 +-- .../resources/admin/ScopeMappedResource.java | 32 ++-- .../testsuite/account/AccountTest.java | 2 +- .../keycloak/testsuite/model/ImportTest.java | 3 +- .../testsuite/oauth/AccessTokenTest.java | 160 +++++++++++++++++- 42 files changed, 1294 insertions(+), 132 deletions(-) create mode 100755 forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-scope-mappings.html create mode 100755 model/api/src/main/java/org/keycloak/models/ScopeContainerModel.java delete mode 100755 model/api/src/main/java/org/keycloak/models/ScopeMapperModel.java create mode 100755 model/jpa/src/main/java/org/keycloak/models/jpa/entities/TemplateScopeMappingEntity.java diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.8.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.8.0.xml index db2a791fcc..068d4e7187 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.8.0.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.8.0.xml @@ -15,6 +15,17 @@ + + + + + + + + + + + @@ -24,12 +35,21 @@ + + + + + + + + + - + @@ -46,6 +66,9 @@ + + + diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml index 36f33bfe76..b2af448261 100755 --- a/connections/jpa/src/main/resources/META-INF/persistence.xml +++ b/connections/jpa/src/main/resources/META-INF/persistence.xml @@ -36,6 +36,7 @@ org.keycloak.models.jpa.entities.GroupRoleMappingEntity org.keycloak.models.jpa.entities.UserGroupMembershipEntity org.keycloak.models.jpa.entities.ClientTemplateEntity + org.keycloak.models.jpa.entities.TemplateScopeMappingEntity org.keycloak.events.jpa.EventEntity diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java index 2384a2ed3b..ab81db5405 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java @@ -41,6 +41,10 @@ public class ClientRepresentation { protected Map registeredNodes; protected List protocolMappers; protected String clientTemplate; + private Boolean useTemplateConfig; + private Boolean useTemplateScope; + private Boolean useTemplateMappers; + public String getId() { return id; @@ -298,4 +302,29 @@ public class ClientRepresentation { public void setClientTemplate(String clientTemplate) { this.clientTemplate = clientTemplate; } + + public Boolean isUseTemplateConfig() { + return useTemplateConfig; + } + + public void setUseTemplateConfig(Boolean useTemplateConfig) { + this.useTemplateConfig = useTemplateConfig; + } + + public Boolean isUseTemplateScope() { + return useTemplateScope; + } + + public void setUseTemplateScope(Boolean useTemplateScope) { + this.useTemplateScope = useTemplateScope; + } + + public Boolean isUseTemplateMappers() { + return useTemplateMappers; + } + + public void setUseTemplateMappers(Boolean useTemplateMappers) { + this.useTemplateMappers = useTemplateMappers; + } + } diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientTemplateRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientTemplateRepresentation.java index f0bf09e392..dc575c4109 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ClientTemplateRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientTemplateRepresentation.java @@ -16,6 +16,7 @@ public class ClientTemplateRepresentation { protected String name; protected String description; protected String protocol; + protected Boolean fullScopeAllowed; protected List protocolMappers; public String getId() { @@ -58,4 +59,12 @@ public class ClientTemplateRepresentation { public void setProtocol(String protocol) { this.protocol = protocol; } + + public Boolean isFullScopeAllowed() { + return fullScopeAllowed; + } + + public void setFullScopeAllowed(Boolean fullScopeAllowed) { + this.fullScopeAllowed = fullScopeAllowed; + } } diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index f7a3c25708..cebb3d4cf9 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -1088,6 +1088,9 @@ module.config([ '$routeProvider', function($routeProvider) { client : function(ClientLoader) { return ClientLoader(); }, + templates : function(ClientTemplateListLoader) { + return ClientTemplateListLoader(); + }, clients : function(ClientListLoader) { return ClientListLoader(); } @@ -1202,6 +1205,21 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'ClientTemplateDetailCtrl' }) + .when('/realms/:realm/client-templates/:template/scope-mappings', { + templateUrl : resourceUrl + '/partials/client-template-scope-mappings.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + template : function(ClientTemplateLoader) { + return ClientTemplateLoader(); + }, + clients : function(ClientListLoader) { + return ClientListLoader(); + } + }, + controller : 'ClientTemplateScopeMappingCtrl' + }) .when('/realms/:realm/clients', { templateUrl : resourceUrl + '/partials/client-list.html', resolve : { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index a6a9130f4a..ec28ef45b0 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -1089,8 +1089,8 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, }; }); -module.controller('ClientScopeMappingCtrl', function($scope, $http, realm, client, clients, Notifications, - Client, +module.controller('ClientScopeMappingCtrl', function($scope, $http, realm, client, clients, templates, Notifications, + Client, ClientTemplate, ClientRealmScopeMapping, ClientClientScopeMapping, ClientRole, ClientAvailableRealmScopeMapping, ClientAvailableClientScopeMapping, ClientCompositeRealmScopeMapping, ClientCompositeClientScopeMapping) { @@ -1107,8 +1107,20 @@ module.controller('ClientScopeMappingCtrl', function($scope, $http, realm, clien $scope.clientMappings = []; $scope.dummymodel = []; + if (client.clientTemplate) { + for (var i = 0; i < templates.length; i++) { + if (templates[i].name == client.clientTemplate) { + ClientTemplate.get({realm: realm.realm, template: templates[i].id}, function(data) { + $scope.template = data; + }); + break; + } + } - $scope.changeFullScopeAllowed = function() { + } + + + $scope.changeFlag = function() { Client.update({ realm : realm.realm, client : client.id @@ -1122,6 +1134,7 @@ module.controller('ClientScopeMappingCtrl', function($scope, $http, realm, clien + function updateRealmRoles() { $scope.realmRoles = ClientAvailableRealmScopeMapping.query({realm : realm.realm, client : client.id}); $scope.realmMappings = ClientRealmScopeMapping.query({realm : realm.realm, client : client.id}); @@ -1420,6 +1433,7 @@ module.controller('AddBuiltinProtocolMapperCtrl', function($scope, realm, client }); module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client, templates, serverInfo, + Client, ClientProtocolMappersByProtocol, ClientProtocolMapper, $route, Dialog, Notifications) { $scope.realm = realm; @@ -1435,6 +1449,16 @@ module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client } } } + $scope.changeFlag = function() { + Client.update({ + realm : realm.realm, + client : client.id + }, $scope.client, function() { + $scope.changed = false; + client = angular.copy($scope.client); + Notifications.success("Client updated."); + }); + } var protocolMappers = serverInfo.protocolMapperTypes[client.protocol]; var mapperTypes = {}; @@ -1910,6 +1934,104 @@ module.controller('ClientTemplateAddBuiltinProtocolMapperCtrl', function($scope, }); +module.controller('ClientTemplateScopeMappingCtrl', function($scope, $http, realm, template, clients, Notifications, + ClientTemplate, + ClientTemplateRealmScopeMapping, ClientTemplateClientScopeMapping, ClientRole, + ClientTemplateAvailableRealmScopeMapping, ClientTemplateAvailableClientScopeMapping, + ClientTemplateCompositeRealmScopeMapping, ClientTemplateCompositeClientScopeMapping) { + $scope.realm = realm; + $scope.template = angular.copy(template); + $scope.selectedRealmRoles = []; + $scope.selectedRealmMappings = []; + $scope.realmMappings = []; + $scope.clients = clients; + $scope.clientRoles = []; + $scope.clientComposite = []; + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.clientMappings = []; + $scope.dummymodel = []; + + + $scope.changeFullScopeAllowed = function() { + ClientTemplate.update({ + realm : realm.realm, + template : template.id + }, $scope.template, function() { + $scope.changed = false; + template = angular.copy($scope.template); + updateTemplateRealmRoles(); + Notifications.success("Scope mappings updated."); + }); + } + + + + function updateTemplateRealmRoles() { + $scope.realmRoles = ClientTemplateAvailableRealmScopeMapping.query({realm : realm.realm, template : template.id}); + $scope.realmMappings = ClientTemplateRealmScopeMapping.query({realm : realm.realm, template : template.id}); + $scope.realmComposite = ClientTemplateCompositeRealmScopeMapping.query({realm : realm.realm, template : template.id}); + } + + function updateTemplateClientRoles() { + if ($scope.targetClient) { + $scope.clientRoles = ClientTemplateAvailableClientScopeMapping.query({realm : realm.realm, template : template.id, targetClient : $scope.targetClient.id}); + $scope.clientMappings = ClientTemplateClientScopeMapping.query({realm : realm.realm, template : template.id, targetClient : $scope.targetClient.id}); + $scope.clientComposite = ClientTemplateCompositeClientScopeMapping.query({realm : realm.realm, template : template.id, targetClient : $scope.targetClient.id}); + } else { + $scope.clientRoles = null; + $scope.clientMappings = null; + $scope.clientComposite = null; + } + } + + $scope.changeClient = function() { + updateTemplateClientRoles(); + }; + + $scope.addRealmRole = function() { + var roles = $scope.selectedRealmRoles; + $scope.selectedRealmRoles = []; + $http.post(authUrl + '/admin/realms/' + realm.realm + '/client-templates/' + template.id + '/scope-mappings/realm', + roles).success(function() { + updateTemplateRealmRoles(); + Notifications.success("Scope mappings updated."); + }); + }; + + $scope.deleteRealmRole = function() { + var roles = $scope.selectedRealmMappings; + $scope.selectedRealmMappings = []; + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/client-templates/' + template.id + '/scope-mappings/realm', + {data : roles, headers : {"content-type" : "application/json"}}).success(function () { + updateTemplateRealmRoles(); + Notifications.success("Scope mappings updated."); + }); + }; + + $scope.addClientRole = function() { + var roles = $scope.selectedClientRoles; + $scope.selectedClientRoles = []; + $http.post(authUrl + '/admin/realms/' + realm.realm + '/client-templates/' + template.id + '/scope-mappings/clients/' + $scope.targetClient.id, + roles).success(function () { + updateTemplateClientRoles(); + Notifications.success("Scope mappings updated."); + }); + }; + + $scope.deleteClientRole = function() { + var roles = $scope.selectedClientMappings; + $scope.selectedClientMappings = []; + $http.delete(authUrl + '/admin/realms/' + realm.realm + '/client-templates/' + template.id + '/scope-mappings/clients/' + $scope.targetClient.id, + {data : roles, headers : {"content-type" : "application/json"}}).success(function () { + updateTemplateClientRoles(); + Notifications.success("Scope mappings updated."); + }); + }; + + updateTemplateRealmRoles(); +}); + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js index 7a47a83a0c..b0d95673e7 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -848,6 +848,52 @@ module.factory('ClientTemplateProtocolMappersByProtocol', function($resource) { }); }); +module.factory('ClientTemplateRealmScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-templates/:template/scope-mappings/realm', { + realm : '@realm', + template : '@template' + }); +}); + +module.factory('ClientTemplateAvailableRealmScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-templates/:template/scope-mappings/realm/available', { + realm : '@realm', + template : '@template' + }); +}); + +module.factory('ClientTemplateCompositeRealmScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-templates/:template/scope-mappings/realm/composite', { + realm : '@realm', + template : '@template' + }); +}); + +module.factory('ClientTemplateClientScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-templates/:template/scope-mappings/clients/:targetClient', { + realm : '@realm', + template : '@template', + targetClient : '@targetClient' + }); +}); + +module.factory('ClientTemplateAvailableClientScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-templates/:template/scope-mappings/clients/:targetClient/available', { + realm : '@realm', + template : '@template', + targetClient : '@targetClient' + }); +}); + +module.factory('ClientTemplateCompositeClientScopeMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/client-templates/:template/scope-mappings/clients/:targetClient/composite', { + realm : '@realm', + template : '@template', + targetClient : '@targetClient' + }); +}); + + module.factory('ClientSessionStats', function($resource) { return $resource(authUrl + '/admin/realms/:realm/clients/:client/session-stats', { realm : '@realm', diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html index 9b98065eba..86b52e103c 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html @@ -7,6 +7,20 @@ +
+
+
+ + Inherit mappers from client template +
+ +
+ +
+
+
@@ -24,7 +38,6 @@ diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html index cd63f49afe..8075065287 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html @@ -11,17 +11,37 @@

-
+
+ + Inherit scope from client template +
+ +
+ +
+
{{:: 'full-scope-allowed.tooltip' | translate}}
- + +
+
+
+ + Client template has full scope allowed, which means this client will have the full scope of all roles. +
+ +
+
+ inherited
- +
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-scope-mappings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-scope-mappings.html new file mode 100755 index 0000000000..690307114e --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-scope-mappings.html @@ -0,0 +1,117 @@ +
+ + + + + +

{{template.name}} {{:: 'scope-mappings' | translate}}

+

+ +
+
+ + {{:: 'full-scope-allowed.tooltip' | translate}} +
+ +
+
+
+ + +
+
+ +
+
+
+ + {{:: 'scope.available-roles.tooltip' | translate}} + + + +
+
+ + {{:: 'assigned-roles.tooltip' | translate}} + + +
+
+ + {{:: 'realm.effective-roles.tooltip' | translate}} + +
+
+
+
+ +
+ + +
+
+
{{:: 'select-client-roles.tooltip' | translate}}
+
+
+
+ + {{:: 'assign.available-roles.tooltip' | translate}} + + +
+
+ + {{:: 'client.assigned-roles.tooltip' | translate}} + + +
+
+ + {{:: 'client.effective-roles.tooltip' | translate}} + +
+
+
+
+ +
+ + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-template.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-template.html index 728e0917d7..0bbdaf0a22 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-template.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-template.html @@ -12,5 +12,9 @@ {{:: 'mappers' | translate}} {{:: 'mappers.tooltip' | translate}} +
  • + {{:: 'scope' | translate}} + {{:: 'scope.tooltip' | translate}} +
  • \ No newline at end of file diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientTemplateResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientTemplateResource.java index 0a5d8ff417..a5665cd901 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientTemplateResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientTemplateResource.java @@ -27,6 +27,9 @@ public interface ClientTemplateResource { @Path("protocol-mappers") public ProtocolMappersResource getProtocolMappers(); + @Path("/scope-mappings") + public RoleMappingResource getScopeMappings(); + @GET @Produces(MediaType.APPLICATION_JSON) public ClientTemplateRepresentation toRepresentation(); @@ -37,4 +40,6 @@ public interface ClientTemplateResource { @DELETE public void remove(); + + } \ No newline at end of file diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java index f7e0305a42..475edf205d 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java @@ -8,7 +8,7 @@ import java.util.Set; * @author Bill Burke * @version $Revision: 1 $ */ -public interface ClientModel extends RoleContainerModel, ProtocolMapperContainerModel { +public interface ClientModel extends RoleContainerModel, ProtocolMapperContainerModel, ScopeContainerModel { // COMMON ATTRIBUTES @@ -74,7 +74,6 @@ public interface ClientModel extends RoleContainerModel, ProtocolMapperContaine void updateDefaultRoles(String[] defaultRoles); - Set getClientScopeMappings(ClientModel client); boolean isBearerOnly(); void setBearerOnly(boolean only); @@ -93,9 +92,6 @@ public interface ClientModel extends RoleContainerModel, ProtocolMapperContaine String getRegistrationToken(); void setRegistrationToken(String registrationToken); - boolean isFullScopeAllowed(); - void setFullScopeAllowed(boolean value); - String getProtocol(); void setProtocol(String protocol); @@ -126,16 +122,16 @@ public interface ClientModel extends RoleContainerModel, ProtocolMapperContaine boolean isServiceAccountsEnabled(); void setServiceAccountsEnabled(boolean serviceAccountsEnabled); - Set getScopeMappings(); - void addScopeMapping(RoleModel role); - void deleteScopeMapping(RoleModel role); - Set getRealmScopeMappings(); - boolean hasScope(RoleModel role); - RealmModel getRealm(); ClientTemplateModel getClientTemplate(); void setClientTemplate(ClientTemplateModel template); + boolean useTemplateScope(); + void setUseTemplateScope(boolean flag); + boolean useTemplateMappers(); + void setUseTemplateMappers(boolean flag); + boolean useTemplateConfig(); + void setUseTemplateConfig(boolean flag); /** * Time in seconds since epoc diff --git a/model/api/src/main/java/org/keycloak/models/ClientTemplateModel.java b/model/api/src/main/java/org/keycloak/models/ClientTemplateModel.java index f7d67ac18e..f3c0f59954 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientTemplateModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientTemplateModel.java @@ -8,7 +8,7 @@ import java.util.Set; * @author Bill Burke * @version $Revision: 1 $ */ -public interface ClientTemplateModel extends ProtocolMapperContainerModel { +public interface ClientTemplateModel extends ProtocolMapperContainerModel, ScopeContainerModel { String getId(); String getName(); diff --git a/model/api/src/main/java/org/keycloak/models/ScopeContainerModel.java b/model/api/src/main/java/org/keycloak/models/ScopeContainerModel.java new file mode 100755 index 0000000000..9bc99ada5c --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/ScopeContainerModel.java @@ -0,0 +1,24 @@ +package org.keycloak.models; + +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface ScopeContainerModel { + boolean isFullScopeAllowed(); + + void setFullScopeAllowed(boolean value); + + Set getScopeMappings(); + + void addScopeMapping(RoleModel role); + + void deleteScopeMapping(RoleModel role); + + Set getRealmScopeMappings(); + + boolean hasScope(RoleModel role); + +} diff --git a/model/api/src/main/java/org/keycloak/models/ScopeMapperModel.java b/model/api/src/main/java/org/keycloak/models/ScopeMapperModel.java deleted file mode 100755 index 4619eb4745..0000000000 --- a/model/api/src/main/java/org/keycloak/models/ScopeMapperModel.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.keycloak.models; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public interface ScopeMapperModel { - -} diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java index 24e5fc655a..04daf9f3aa 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java @@ -49,6 +49,9 @@ public class ClientEntity extends AbstractIdentifiableEntity { private List identityProviders = new ArrayList(); private List protocolMappers = new ArrayList(); private String clientTemplate; + private boolean useTemplateConfig; + private boolean useTemplateScope; + private boolean useTemplateMappers; public String getClientId() { return clientId; @@ -309,5 +312,29 @@ public class ClientEntity extends AbstractIdentifiableEntity { public void setClientTemplate(String clientTemplate) { this.clientTemplate = clientTemplate; } + + public boolean isUseTemplateConfig() { + return useTemplateConfig; + } + + public void setUseTemplateConfig(boolean useTemplateConfig) { + this.useTemplateConfig = useTemplateConfig; + } + + public boolean isUseTemplateScope() { + return useTemplateScope; + } + + public void setUseTemplateScope(boolean useTemplateScope) { + this.useTemplateScope = useTemplateScope; + } + + public boolean isUseTemplateMappers() { + return useTemplateMappers; + } + + public void setUseTemplateMappers(boolean useTemplateMappers) { + this.useTemplateMappers = useTemplateMappers; + } } diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientTemplateEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientTemplateEntity.java index 849eaa12e9..8ca932f0f1 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/ClientTemplateEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/ClientTemplateEntity.java @@ -14,6 +14,8 @@ public class ClientTemplateEntity extends AbstractIdentifiableEntity { private String description; private String realmId; private String protocol; + private boolean fullScopeAllowed; + private List scopeIds = new ArrayList(); private List protocolMappers = new ArrayList(); public String getName() { @@ -55,5 +57,21 @@ public class ClientTemplateEntity extends AbstractIdentifiableEntity { public void setProtocol(String protocol) { this.protocol = protocol; } + + public boolean isFullScopeAllowed() { + return fullScopeAllowed; + } + + public void setFullScopeAllowed(boolean fullScopeAllowed) { + this.fullScopeAllowed = fullScopeAllowed; + } + + public List getScopeIds() { + return scopeIds; + } + + public void setScopeIds(List scopeIds) { + this.scopeIds = scopeIds; + } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index 8c6af8862e..f08eee4289 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -15,7 +15,9 @@ import org.keycloak.models.KeycloakSessionTask; import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.ScopeContainerModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProviderModel; @@ -38,6 +40,7 @@ import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -521,4 +524,20 @@ public final class KeycloakModelUtils { } return found; } + + public static Set getClientScopeMappings(ClientModel client, ScopeContainerModel container) { + Set mappings = container.getScopeMappings(); + Set result = new HashSet<>(); + for (RoleModel role : mappings) { + RoleContainerModel roleContainer = role.getContainer(); + if (roleContainer instanceof ClientModel) { + if (client.getId().equals(((ClientModel)roleContainer).getId())) { + result.add(role); + } + + } + } + return result; + } + } diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 7b6a198d76..e891582a63 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -419,6 +419,7 @@ public class ModelToRepresentation { } rep.setProtocolMappers(mappings); } + rep.setFullScopeAllowed(clientModel.isFullScopeAllowed()); return rep; } @@ -476,6 +477,9 @@ public class ModelToRepresentation { } rep.setProtocolMappers(mappings); } + rep.setUseTemplateMappers(clientModel.useTemplateMappers()); + rep.setUseTemplateConfig(clientModel.useTemplateConfig()); + rep.setUseTemplateScope(clientModel.useTemplateScope()); return rep; } 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 4c7dad1189..2d99c3b2a3 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 @@ -802,11 +802,6 @@ public class RepresentationToModel { if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient()); if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout()); if (resourceRep.getProtocol() != null) client.setProtocol(resourceRep.getProtocol()); - if (resourceRep.isFullScopeAllowed() != null) { - client.setFullScopeAllowed(resourceRep.isFullScopeAllowed()); - } else { - client.setFullScopeAllowed(!client.isConsentRequired()); - } if (resourceRep.getNodeReRegistrationTimeout() != null) { client.setNodeReRegistrationTimeout(resourceRep.getNodeReRegistrationTimeout()); } else { @@ -893,6 +888,26 @@ public class RepresentationToModel { } } + if (resourceRep.isFullScopeAllowed() != null) { + client.setFullScopeAllowed(resourceRep.isFullScopeAllowed()); + } else { + if (client.getClientTemplate() != null) { + client.setFullScopeAllowed(!client.isConsentRequired() && client.getClientTemplate().isFullScopeAllowed()); + + } else { + client.setFullScopeAllowed(!client.isConsentRequired()); + } + } + if (resourceRep.isUseTemplateConfig() != null) client.setUseTemplateConfig(resourceRep.isUseTemplateConfig()); + else client.setUseTemplateConfig(resourceRep.getClientTemplate() != null); + + if (resourceRep.isUseTemplateScope() != null) client.setUseTemplateScope(resourceRep.isUseTemplateScope()); + else client.setUseTemplateScope(resourceRep.getClientTemplate() != null); + + if (resourceRep.isUseTemplateMappers() != null) client.setUseTemplateMappers(resourceRep.isUseTemplateMappers()); + else client.setUseTemplateMappers(resourceRep.getClientTemplate() != null); + + return client; } @@ -949,14 +964,23 @@ public class RepresentationToModel { } } + if (rep.isUseTemplateConfig() != null) resource.setUseTemplateConfig(rep.isUseTemplateConfig()); + if (rep.isUseTemplateScope() != null) resource.setUseTemplateScope(rep.isUseTemplateScope()); + if (rep.isUseTemplateMappers() != null) resource.setUseTemplateMappers(rep.isUseTemplateMappers()); + + if (rep.getClientTemplate() != null) { if (rep.getClientTemplate().equals(ClientTemplateRepresentation.NONE)) { resource.setClientTemplate(null); } else { RealmModel realm = resource.getRealm(); for (ClientTemplateModel template : realm.getClientTemplates()) { + if (template.getName().equals(rep.getClientTemplate())) { resource.setClientTemplate(template); + if (rep.isUseTemplateConfig() == null) resource.setUseTemplateConfig(true); + if (rep.isUseTemplateScope() == null) resource.setUseTemplateScope(true); + if (rep.isUseTemplateMappers() == null) resource.setUseTemplateMappers(true); break; } } @@ -984,7 +1008,7 @@ public class RepresentationToModel { if (resourceRep.getName() != null) client.setName(resourceRep.getName()); if(resourceRep.getDescription() != null) client.setDescription(resourceRep.getDescription()); if (resourceRep.getProtocol() != null) client.setProtocol(resourceRep.getProtocol()); - + if (resourceRep.isFullScopeAllowed() != null) client.setFullScopeAllowed(resourceRep.isFullScopeAllowed()); if (resourceRep.getProtocolMappers() != null) { // first, remove all default/built in mappers Set mappers = client.getProtocolMappers(); @@ -1001,6 +1025,9 @@ public class RepresentationToModel { public static void updateClientTemplate(ClientTemplateRepresentation rep, ClientTemplateModel resource) { if (rep.getName() != null) resource.setName(rep.getName()); if (rep.getDescription() != null) resource.setDescription(rep.getDescription()); + if (rep.isFullScopeAllowed() != null) { + resource.setFullScopeAllowed(rep.isFullScopeAllowed()); + } if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol()); diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java index d96e6bcace..164fc74636 100755 --- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java +++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java @@ -69,6 +69,47 @@ public class ClientAdapter implements ClientModel { } + @Override + public boolean useTemplateScope() { + if (updated != null) return updated.useTemplateScope(); + return cached.isUseTemplateScope(); + } + + @Override + public void setUseTemplateScope(boolean value) { + getDelegateForUpdate(); + updated.setUseTemplateScope(value); + + } + + @Override + public boolean useTemplateConfig() { + if (updated != null) return updated.useTemplateConfig(); + return cached.isUseTemplateConfig(); + } + + @Override + public void setUseTemplateConfig(boolean value) { + getDelegateForUpdate(); + updated.setUseTemplateConfig(value); + + } + + @Override + public boolean useTemplateMappers() { + if (updated != null) return updated.useTemplateMappers(); + return cached.isUseTemplateMappers(); + } + + @Override + public void setUseTemplateMappers(boolean value) { + getDelegateForUpdate(); + updated.setUseTemplateMappers(value); + + } + + + public void addWebOrigin(String webOrigin) { getDelegateForUpdate(); updated.addWebOrigin(webOrigin); @@ -412,25 +453,6 @@ public class ClientAdapter implements ClientModel { updated.updateDefaultRoles(defaultRoles); } - @Override - public Set getClientScopeMappings(ClientModel client) { - Set roleMappings = client.getScopeMappings(); - - Set appRoles = new HashSet(); - for (RoleModel role : roleMappings) { - RoleContainerModel container = role.getContainer(); - if (container instanceof RealmModel) { - } else { - ClientModel app = (ClientModel)container; - if (app.getId().equals(getId())) { - appRoles.add(role); - } - } - } - - return appRoles; - } - @Override public boolean isBearerOnly() { if (updated != null) return updated.isBearerOnly(); diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java index 2a1674d9ad..13b68aa881 100755 --- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java +++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java @@ -132,6 +132,70 @@ public class ClientTemplateAdapter implements ClientTemplateModel { updated.setProtocol(protocol); } + @Override + public boolean isFullScopeAllowed() { + if (updated != null) return updated.isFullScopeAllowed(); + return cached.isFullScopeAllowed(); + } + + @Override + public void setFullScopeAllowed(boolean value) { + getDelegateForUpdate(); + updated.setFullScopeAllowed(value); + + } + + public Set getScopeMappings() { + if (updated != null) return updated.getScopeMappings(); + Set roles = new HashSet(); + for (String id : cached.getScope()) { + roles.add(cacheSession.getRoleById(id, getRealm())); + + } + return roles; + } + + public void addScopeMapping(RoleModel role) { + getDelegateForUpdate(); + updated.addScopeMapping(role); + } + + public void deleteScopeMapping(RoleModel role) { + getDelegateForUpdate(); + updated.deleteScopeMapping(role); + } + + public Set getRealmScopeMappings() { + Set roleMappings = getScopeMappings(); + + Set appRoles = new HashSet(); + for (RoleModel role : roleMappings) { + RoleContainerModel container = role.getContainer(); + if (container instanceof RealmModel) { + if (((RealmModel) container).getId().equals(cachedRealm.getId())) { + appRoles.add(role); + } + } + } + + return appRoles; + } + + @Override + public boolean hasScope(RoleModel role) { + if (updated != null) return updated.hasScope(role); + if (cached.isFullScopeAllowed() || cached.getScope().contains(role.getId())) return true; + + Set roles = getScopeMappings(); + + for (RoleModel mapping : roles) { + if (mapping.hasRole(role)) return true; + } + return false; + } + + + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java index dbf47544ab..ab83077a27 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java @@ -56,6 +56,9 @@ public class CachedClient implements Serializable { private int nodeReRegistrationTimeout; private Map registeredNodes; private String clientTemplate; + private boolean useTemplateScope; + private boolean useTemplateConfig; + private boolean useTemplateMappers; public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) { id = model.getId(); @@ -102,6 +105,9 @@ public class CachedClient implements Serializable { if (model.getClientTemplate() != null) { clientTemplate = model.getClientTemplate().getId(); } + useTemplateConfig = model.useTemplateConfig(); + useTemplateMappers = model.useTemplateMappers(); + useTemplateScope = model.useTemplateScope(); } public String getId() { return id; @@ -238,4 +244,16 @@ public class CachedClient implements Serializable { public String getClientTemplate() { return clientTemplate; } + + public boolean isUseTemplateScope() { + return useTemplateScope; + } + + public boolean isUseTemplateConfig() { + return useTemplateConfig; + } + + public boolean isUseTemplateMappers() { + return useTemplateMappers; + } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClientTemplate.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClientTemplate.java index 2df28c161a..58bcc126cd 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClientTemplate.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClientTemplate.java @@ -28,6 +28,8 @@ public class CachedClientTemplate implements Serializable { private String description; private String realm; private String protocol; + private boolean fullScopeAllowed; + private Set scope = new HashSet(); private Set protocolMappers = new HashSet(); public CachedClientTemplate(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientTemplateModel model) { @@ -36,9 +38,13 @@ public class CachedClientTemplate implements Serializable { description = model.getDescription(); this.realm = realm.getId(); protocol = model.getProtocol(); + fullScopeAllowed = model.isFullScopeAllowed(); for (ProtocolMapperModel mapper : model.getProtocolMappers()) { this.protocolMappers.add(mapper); } + for (RoleModel role : model.getScopeMappings()) { + scope.add(role.getId()); + } } public String getId() { return id; @@ -63,4 +69,12 @@ public class CachedClientTemplate implements Serializable { public String getProtocol() { return protocol; } + + public boolean isFullScopeAllowed() { + return fullScopeAllowed; + } + + public Set getScope() { + return scope; + } } 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 a8abd1137b..0d9eea4c7d 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 @@ -206,7 +206,7 @@ public class ClientAdapter implements ClientModel { } @Override - public Set getRealmScopeMappings() { + public Set getRealmScopeMappings() { Set roleMappings = getScopeMappings(); Set appRoles = new HashSet<>(); @@ -238,7 +238,8 @@ public class ClientAdapter implements ClientModel { @Override public void addScopeMapping(RoleModel role) { - if (hasScope(role)) return; + Set roles = getScopeMappings(); + if (roles.contains(role)) return; ScopeMappingEntity entity = new ScopeMappingEntity(); entity.setClient(getEntity()); RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em); @@ -319,6 +320,39 @@ public class ClientAdapter implements ClientModel { } + @Override + public boolean useTemplateScope() { + return entity.isUseTemplateScope(); + } + + @Override + public void setUseTemplateScope(boolean flag) { + entity.setUseTemplateScope(flag); + + } + + @Override + public boolean useTemplateMappers() { + return entity.isUseTemplateMappers(); + } + + @Override + public void setUseTemplateMappers(boolean flag) { + entity.setUseTemplateMappers(flag); + + } + + @Override + public boolean useTemplateConfig() { + return entity.isUseTemplateConfig(); + } + + @Override + public void setUseTemplateConfig(boolean flag) { + entity.setUseTemplateConfig(flag); + + } + public static boolean contains(String str, String[] array) { for (String s : array) { if (str.equals(s)) return true; @@ -604,6 +638,7 @@ public class ClientAdapter implements ClientModel { String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em); em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", role).executeUpdate(); em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", role).executeUpdate(); + em.createNamedQuery("deleteTemplateScopeMappingByRole").setParameter("role", role).executeUpdate(); role.setClient(null); em.flush(); em.remove(role); @@ -641,28 +676,6 @@ public class ClientAdapter implements ClientModel { return false; } - @Override - public Set getClientScopeMappings(ClientModel client) { - Set roleMappings = client.getScopeMappings(); - - Set appRoles = new HashSet(); - for (RoleModel role : roleMappings) { - RoleContainerModel container = role.getContainer(); - if (container instanceof RealmModel) { - } else { - ClientModel app = (ClientModel)container; - if (app.getId().equals(getId())) { - appRoles.add(role); - } - } - } - - return appRoles; - } - - - - @Override public List getDefaultRoles() { Collection entities = entity.getDefaultRoles(); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java index 8e92e79dc8..2d5c78b7c2 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java @@ -14,6 +14,7 @@ import org.keycloak.models.jpa.entities.ClientTemplateEntity; import org.keycloak.models.jpa.entities.ProtocolMapperEntity; import org.keycloak.models.jpa.entities.RoleEntity; import org.keycloak.models.jpa.entities.ScopeMappingEntity; +import org.keycloak.models.jpa.entities.TemplateScopeMappingEntity; import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; @@ -203,6 +204,88 @@ public class ClientTemplateAdapter implements ClientTemplateModel { return mapping; } + @Override + public boolean isFullScopeAllowed() { + return entity.isFullScopeAllowed(); + } + + @Override + public void setFullScopeAllowed(boolean value) { + entity.setFullScopeAllowed(value); + } + + @Override + public Set getRealmScopeMappings() { + Set roleMappings = getScopeMappings(); + + Set appRoles = new HashSet<>(); + for (RoleModel role : roleMappings) { + RoleContainerModel container = role.getContainer(); + if (container instanceof RealmModel) { + if (((RealmModel) container).getId().equals(realm.getId())) { + appRoles.add(role); + } + } + } + + return appRoles; + } + + @Override + public Set getScopeMappings() { + TypedQuery query = em.createNamedQuery("clientTemplateScopeMappingIds", String.class); + query.setParameter("template", getEntity()); + List ids = query.getResultList(); + Set roles = new HashSet(); + for (String roleId : ids) { + RoleModel role = realm.getRoleById(roleId); + if (role == null) continue; + roles.add(role); + } + return roles; + } + + @Override + public void addScopeMapping(RoleModel role) { + if (hasScope(role)) return; + TemplateScopeMappingEntity entity = new TemplateScopeMappingEntity(); + entity.setTemplate(getEntity()); + RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em); + entity.setRole(roleEntity); + em.persist(entity); + em.flush(); + em.detach(entity); + } + + @Override + public void deleteScopeMapping(RoleModel role) { + TypedQuery query = getRealmScopeMappingQuery(role); + List results = query.getResultList(); + if (results.size() == 0) return; + for (TemplateScopeMappingEntity entity : results) { + em.remove(entity); + } + } + + protected TypedQuery getRealmScopeMappingQuery(RoleModel role) { + TypedQuery query = em.createNamedQuery("templateHasScope", TemplateScopeMappingEntity.class); + query.setParameter("template", getEntity()); + RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em); + query.setParameter("role", roleEntity); + return query; + } + + @Override + public boolean hasScope(RoleModel role) { + if (isFullScopeAllowed()) return true; + Set roles = getScopeMappings(); + if (roles.contains(role)) return true; + + for (RoleModel mapping : roles) { + if (mapping.hasRole(role)) return true; + } + return false; + } @Override public boolean equals(Object o) { @@ -219,4 +302,6 @@ public class ClientTemplateAdapter implements ClientTemplateModel { } + + } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java index 25df437f5b..25e79f8ae8 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java @@ -110,6 +110,9 @@ public class JpaRealmProvider implements RealmProvider { for (ClientEntity a : new LinkedList<>(realm.getClients())) { adapter.removeClient(a.getId()); } + for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) { + adapter.removeClientTemplate(a.getId()); + } em.remove(realm); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 4f96829e61..ccc333985d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -1051,6 +1051,7 @@ public class RealmAdapter implements RealmModel { String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em); em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate(); em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", roleEntity).executeUpdate(); + em.createNamedQuery("deleteTemplateScopeMappingByRole").setParameter("role", roleEntity).executeUpdate(); em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", roleEntity.getId()).executeUpdate(); em.remove(roleEntity); @@ -2146,9 +2147,12 @@ public class RealmAdapter implements RealmModel { if (client == null) { return false; } + em.createNamedQuery("deleteTemplateScopeMappingByClient").setParameter("template", clientEntity).executeUpdate(); + em.flush(); em.remove(clientEntity); em.flush(); + return true; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java index 6767e7b20c..80781b8ac4 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java @@ -61,6 +61,15 @@ public class ClientEntity { @JoinColumn(name = "CLIENT_TEMPLATE_ID") protected ClientTemplateEntity clientTemplate; + @Column(name="USE_TEMPLATE_CONFIG") + private boolean useTemplateConfig; + + @Column(name="USE_TEMPLATE_SCOPE") + private boolean useTemplateScope; + + @Column(name="USE_TEMPLATE_MAPPERS") + private boolean useTemplateMappers; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "REALM_ID") protected RealmEntity realm; @@ -404,4 +413,28 @@ public class ClientEntity { public void setClientTemplate(ClientTemplateEntity clientTemplate) { this.clientTemplate = clientTemplate; } + + public boolean isUseTemplateConfig() { + return useTemplateConfig; + } + + public void setUseTemplateConfig(boolean useTemplateConfig) { + this.useTemplateConfig = useTemplateConfig; + } + + public boolean isUseTemplateScope() { + return useTemplateScope; + } + + public void setUseTemplateScope(boolean useTemplateScope) { + this.useTemplateScope = useTemplateScope; + } + + public boolean isUseTemplateMappers() { + return useTemplateMappers; + } + + public void setUseTemplateMappers(boolean useTemplateMappers) { + this.useTemplateMappers = useTemplateMappers; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java index ff4bd14c0e..5da01c4c57 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java @@ -45,6 +45,8 @@ public class ClientTemplateEntity { @Column(name="PROTOCOL") private String protocol; + @Column(name="FULL_SCOPE_ALLOWED") + private boolean fullScopeAllowed; public RealmEntity getRealm() { return realm; @@ -93,4 +95,12 @@ public class ClientTemplateEntity { public void setProtocol(String protocol) { this.protocol = protocol; } + + public boolean isFullScopeAllowed() { + return fullScopeAllowed; + } + + public void setFullScopeAllowed(boolean fullScopeAllowed) { + this.fullScopeAllowed = fullScopeAllowed; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/TemplateScopeMappingEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/TemplateScopeMappingEntity.java new file mode 100755 index 0000000000..375fd0578c --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/TemplateScopeMappingEntity.java @@ -0,0 +1,99 @@ +package org.keycloak.models.jpa.entities; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="templateHasScope", query="select m from TemplateScopeMappingEntity m where m.template = :template and m.role = :role"), + @NamedQuery(name="clientTemplateScopeMappings", query="select m from TemplateScopeMappingEntity m where m.template = :template"), + @NamedQuery(name="clientTemplateScopeMappingIds", query="select m.role.id from TemplateScopeMappingEntity m where m.template = :template"), + @NamedQuery(name="deleteTemplateScopeMappingByRole", query="delete from TemplateScopeMappingEntity where role = :role"), + @NamedQuery(name="deleteTemplateScopeMappingByClient", query="delete from TemplateScopeMappingEntity where template = :template") +}) +@Table(name="TEMPLATE_SCOPE_MAPPING") +@Entity +@IdClass(TemplateScopeMappingEntity.Key.class) +public class TemplateScopeMappingEntity { + + @Id + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name = "TEMPLATE_ID") + protected ClientTemplateEntity template; + + @Id + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name="ROLE_ID") + protected RoleEntity role; + + public ClientTemplateEntity getTemplate() { + return template; + } + + public void setTemplate(ClientTemplateEntity template) { + this.template = template; + } + + public RoleEntity getRole() { + return role; + } + + public void setRole(RoleEntity role) { + this.role = role; + } + + public static class Key implements Serializable { + + protected ClientTemplateEntity template; + + protected RoleEntity role; + + public Key() { + } + + public Key(ClientTemplateEntity template, RoleEntity role) { + this.template = template; + this.role = role; + } + + public ClientTemplateEntity getTemplate() { + return template; + } + + public RoleEntity getRole() { + return role; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (template != null ? !template.getId().equals(key.template != null ? key.template.getId() : null) : key.template != null) return false; + if (role != null ? !role.getId().equals(key.role != null ? key.role.getId() : null) : key.role != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = template != null ? template.getId().hashCode() : 0; + result = 31 * result + (role != null ? role.getId().hashCode() : 0); + return result; + } + } + +} 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 af448c4018..02ccea48f2 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 @@ -10,6 +10,7 @@ import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.ScopeContainerModel; import org.keycloak.models.entities.ProtocolMapperEntity; import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity; import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; @@ -620,19 +621,6 @@ public class ClientAdapter extends AbstractMongoAdapter imple return false; } - @Override - public Set getClientScopeMappings(ClientModel client) { - Set result = new HashSet(); - List roles = MongoModelUtils.getAllScopesOfClient(client, invocationContext); - - for (MongoRoleEntity role : roles) { - if (getId().equals(role.getClientId())) { - result.add(new RoleAdapter(session, getRealm(), role, this, invocationContext)); - } - } - return result; - } - @Override public List getDefaultRoles() { return getMongoEntity().getDefaultRoles(); @@ -726,4 +714,41 @@ public class ClientAdapter extends AbstractMongoAdapter imple updateMongoEntity(); } + + @Override + public boolean useTemplateScope() { + return getMongoEntity().isUseTemplateScope(); + } + + @Override + public void setUseTemplateScope(boolean flag) { + getMongoEntity().setUseTemplateScope(flag); + updateMongoEntity(); + + } + + @Override + public boolean useTemplateMappers() { + return getMongoEntity().isUseTemplateMappers(); + } + + @Override + public void setUseTemplateMappers(boolean flag) { + getMongoEntity().setUseTemplateMappers(flag); + updateMongoEntity(); + + } + + @Override + public boolean useTemplateConfig() { + return getMongoEntity().isUseTemplateConfig(); + } + + @Override + public void setUseTemplateConfig(boolean flag) { + getMongoEntity().setUseTemplateConfig(flag); + updateMongoEntity(); + + } + } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientTemplateAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientTemplateAdapter.java index ee19deae45..91725bffd0 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientTemplateAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientTemplateAdapter.java @@ -209,7 +209,70 @@ public class ClientTemplateAdapter extends AbstractMongoAdapter getScopeMappings() { + Set result = new HashSet(); + List roles = MongoModelUtils.getAllScopesOfTemplate(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; + } + + @Override + public Set getRealmScopeMappings() { + Set allScopes = getScopeMappings(); + + // Filter to retrieve just realm roles TODO: Maybe improve to avoid filter programmatically... Maybe have separate fields for realmRoles and appRoles on user? + Set realmRoles = new HashSet(); + for (RoleModel role : allScopes) { + MongoRoleEntity roleEntity = ((RoleAdapter) role).getRole(); + + if (realm.getId().equals(roleEntity.getRealmId())) { + realmRoles.add(role); + } + } + return realmRoles; + } + + @Override + public void addScopeMapping(RoleModel role) { + getMongoStore().pushItemToList(this.getMongoEntity(), "scopeIds", role.getId(), true, invocationContext); + } + + @Override + public void deleteScopeMapping(RoleModel role) { + getMongoStore().pullItemFromList(this.getMongoEntity(), "scopeIds", role.getId(), invocationContext); + } + + @Override + public boolean hasScope(RoleModel role) { + if (isFullScopeAllowed()) return true; + Set roles = getScopeMappings(); + if (roles.contains(role)) return true; + + for (RoleModel mapping : roles) { + if (mapping.hasRole(role)) return true; + } + return false; + } @Override public boolean equals(Object o) { 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 eaf221698a..907b29d26c 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,12 +4,15 @@ 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.ClientTemplateModel; import org.keycloak.models.GroupModel; 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.entities.ClientTemplateEntity; import org.keycloak.models.mongo.keycloak.adapters.ClientAdapter; +import org.keycloak.models.mongo.keycloak.adapters.ClientTemplateAdapter; import org.keycloak.models.mongo.keycloak.adapters.GroupAdapter; import org.keycloak.models.mongo.keycloak.adapters.UserAdapter; import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; @@ -52,6 +55,19 @@ public class MongoModelUtils { return Collections.emptyList(); } + DBObject query = new QueryBuilder() + .and("_id").in(scopeIds) + .get(); + return invContext.getMongoStore().loadEntities(MongoRoleEntity.class, query, invContext); + } + public static List getAllScopesOfTemplate(ClientTemplateModel template, MongoStoreInvocationContext invContext) { + ClientTemplateEntity scopedEntity = ((ClientTemplateAdapter)template).getMongoEntity(); + List scopeIds = scopedEntity.getScopeIds(); + + if (scopeIds == null || scopeIds.isEmpty()) { + return Collections.emptyList(); + } + DBObject query = new QueryBuilder() .and("_id").in(scopeIds) .get(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 346832a38b..d1a83bb127 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -267,7 +267,7 @@ public class TokenManager { Set requestedProtocolMappers = new HashSet(); ClientTemplateModel clientTemplate = client.getClientTemplate(); - if (clientTemplate != null) { + if (clientTemplate != null && client.useTemplateMappers()) { for (ProtocolMapperModel protocolMapper : clientTemplate.getProtocolMappers()) { if (protocolMapper.getProtocol().equals(clientSession.getAuthMethod())) { requestedProtocolMappers.add(protocolMapper.getId()); @@ -322,14 +322,22 @@ public class TokenManager { } + ClientTemplateModel template = client.getClientTemplate(); - if (client.isFullScopeAllowed()) { + boolean useTemplateScope = template != null && client.useTemplateScope(); + + if ( (useTemplateScope && template.isFullScopeAllowed()) || (client.isFullScopeAllowed())) { + logger.debug("Using full scope for client"); requestedRoles = roleMappings; } else { - - Set scopeMappings = client.getScopeMappings(); + Set scopeMappings = new HashSet<>(); + if (useTemplateScope) { + logger.debug("Adding template scope mappings"); + scopeMappings.addAll(template.getScopeMappings()); + } scopeMappings.addAll(client.getRoles()); - + Set clientScopeMappings = client.getScopeMappings(); + scopeMappings.addAll(clientScopeMappings); for (RoleModel role : roleMappings) { for (RoleModel desiredRole : scopeMappings) { Set visited = new HashSet(); @@ -337,7 +345,6 @@ public class TokenManager { } } } - if (applyScopeParam) { Collection scopeParamRoles; if (scopeParam != null) { 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 509d485e31..53cc76b354 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java @@ -67,7 +67,7 @@ public class ClientTemplateResource { protected RealmModel realm; private RealmAuth auth; private AdminEventBuilder adminEvent; - protected ClientTemplateModel client; + protected ClientTemplateModel template; protected KeycloakSession session; @Context @@ -80,10 +80,10 @@ public class ClientTemplateResource { return keycloak; } - public ClientTemplateResource(RealmModel realm, RealmAuth auth, ClientTemplateModel clientModel, KeycloakSession session, AdminEventBuilder adminEvent) { + public ClientTemplateResource(RealmModel realm, RealmAuth auth, ClientTemplateModel template, KeycloakSession session, AdminEventBuilder adminEvent) { this.realm = realm; this.auth = auth; - this.client = clientModel; + this.template = template; this.session = session; this.adminEvent = adminEvent; @@ -92,11 +92,21 @@ public class ClientTemplateResource { @Path("protocol-mappers") public ProtocolMappersResource getProtocolMappers() { - ProtocolMappersResource mappers = new ProtocolMappersResource(client, auth, adminEvent); + ProtocolMappersResource mappers = new ProtocolMappersResource(template, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(mappers); return mappers; } + /** + * Base path for managing the scope mappings for the client + * + * @return + */ + @Path("scope-mappings") + public ScopeMappedResource getScopeMappedResource() { + return new ScopeMappedResource(realm, auth, template, session, adminEvent); + } + /** * Update the client template * @param rep @@ -108,7 +118,7 @@ public class ClientTemplateResource { auth.requireManage(); try { - RepresentationToModel.updateClientTemplate(rep, client); + RepresentationToModel.updateClientTemplate(rep, template); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); return Response.noContent().build(); } catch (ModelDuplicateException e) { @@ -127,7 +137,7 @@ public class ClientTemplateResource { @Produces(MediaType.APPLICATION_JSON) public ClientTemplateRepresentation getClient() { auth.requireView(); - return ModelToRepresentation.toRepresentation(client); + return ModelToRepresentation.toRepresentation(template); } /** @@ -138,7 +148,7 @@ public class ClientTemplateResource { @NoCache public void deleteClientTemplate() { auth.requireManage(); - realm.removeClientTemplate(client.getId()); + realm.removeClientTemplate(template.getId()); adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java index 44b355e037..b90cb0fef7 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedClientResource.java @@ -7,6 +7,8 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.ScopeContainerModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.RoleRepresentation; @@ -29,15 +31,15 @@ import java.util.Set; public class ScopeMappedClientResource { protected RealmModel realm; private RealmAuth auth; - protected ClientModel client; + protected ScopeContainerModel scopeContainer; protected KeycloakSession session; protected ClientModel scopedClient; protected AdminEventBuilder adminEvent; - public ScopeMappedClientResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session, ClientModel scopedClient, AdminEventBuilder adminEvent) { + public ScopeMappedClientResource(RealmModel realm, RealmAuth auth, ScopeContainerModel scopeContainer, KeycloakSession session, ClientModel scopedClient, AdminEventBuilder adminEvent) { this.realm = realm; this.auth = auth; - this.client = client; + this.scopeContainer = scopeContainer; this.session = session; this.scopedClient = scopedClient; this.adminEvent = adminEvent; @@ -56,7 +58,7 @@ public class ScopeMappedClientResource { public List getClientScopeMappings() { auth.requireView(); - Set mappings = scopedClient.getClientScopeMappings(client); + Set mappings = KeycloakModelUtils.getClientScopeMappings(scopedClient, scopeContainer); //scopedClient.getClientScopeMappings(client); List mapRep = new ArrayList(); for (RoleModel roleModel : mappings) { mapRep.add(ModelToRepresentation.toRepresentation(roleModel)); @@ -79,7 +81,7 @@ public class ScopeMappedClientResource { auth.requireView(); Set roles = scopedClient.getRoles(); - return ScopeMappedResource.getAvailable(client, roles); + return ScopeMappedResource.getAvailable(scopeContainer, roles); } /** @@ -97,7 +99,7 @@ public class ScopeMappedClientResource { auth.requireView(); Set roles = scopedClient.getRoles(); - return ScopeMappedResource.getComposite(client, roles); + return ScopeMappedResource.getComposite(scopeContainer, roles); } /** @@ -115,7 +117,7 @@ public class ScopeMappedClientResource { if (roleModel == null) { throw new NotFoundException("Role not found"); } - client.addScopeMapping(roleModel); + scopeContainer.addScopeMapping(roleModel); adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), roleModel.getId()).representation(roles).success(); } } @@ -131,9 +133,9 @@ public class ScopeMappedClientResource { auth.requireManage(); if (roles == null) { - Set roleModels = scopedClient.getClientScopeMappings(client); + Set roleModels = KeycloakModelUtils.getClientScopeMappings(scopedClient, scopeContainer);//scopedClient.getClientScopeMappings(client); for (RoleModel roleModel : roleModels) { - client.deleteScopeMapping(roleModel); + scopeContainer.deleteScopeMapping(roleModel); } adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).representation(roles).success(); } else { @@ -142,7 +144,7 @@ public class ScopeMappedClientResource { if (roleModel == null) { throw new NotFoundException("Role not found"); } - client.deleteScopeMapping(roleModel); + scopeContainer.deleteScopeMapping(roleModel); adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri(), roleModel.getId()).representation(roles).success(); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java index 2d9b6a263b..499392cfd2 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ScopeMappedResource.java @@ -7,6 +7,8 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.ScopeContainerModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.ClientMappingsRepresentation; import org.keycloak.representations.idm.MappingsRepresentation; @@ -36,14 +38,14 @@ import java.util.Set; public class ScopeMappedResource { protected RealmModel realm; private RealmAuth auth; - protected ClientModel client; + protected ScopeContainerModel scopeContainer; protected KeycloakSession session; protected AdminEventBuilder adminEvent; - public ScopeMappedResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session, AdminEventBuilder adminEvent) { + public ScopeMappedResource(RealmModel realm, RealmAuth auth, ScopeContainerModel scopeContainer, KeycloakSession session, AdminEventBuilder adminEvent) { this.realm = realm; this.auth = auth; - this.client = client; + this.scopeContainer = scopeContainer; this.session = session; this.adminEvent = adminEvent; } @@ -60,7 +62,7 @@ public class ScopeMappedResource { auth.requireView(); MappingsRepresentation all = new MappingsRepresentation(); - Set realmMappings = client.getRealmScopeMappings(); + Set realmMappings = scopeContainer.getRealmScopeMappings(); if (realmMappings.size() > 0) { List realmRep = new ArrayList(); for (RoleModel roleModel : realmMappings) { @@ -73,7 +75,7 @@ public class ScopeMappedResource { if (clients.size() > 0) { Map clientMappings = new HashMap(); for (ClientModel client : clients) { - Set roleMappings = client.getClientScopeMappings(this.client); + Set roleMappings = KeycloakModelUtils.getClientScopeMappings(client, this.scopeContainer); //client.getClientScopeMappings(this.client); if (roleMappings.size() > 0) { ClientMappingsRepresentation mappings = new ClientMappingsRepresentation(); mappings.setId(client.getId()); @@ -103,7 +105,7 @@ public class ScopeMappedResource { public List getRealmScopeMappings() { auth.requireView(); - Set realmMappings = client.getRealmScopeMappings(); + Set realmMappings = scopeContainer.getRealmScopeMappings(); List realmMappingsRep = new ArrayList(); for (RoleModel roleModel : realmMappings) { realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel)); @@ -124,10 +126,10 @@ public class ScopeMappedResource { auth.requireView(); Set roles = realm.getRoles(); - return getAvailable(client, roles); + return getAvailable(scopeContainer, roles); } - public static List getAvailable(ClientModel client, Set roles) { + public static List getAvailable(ScopeContainerModel client, Set roles) { List available = new ArrayList(); for (RoleModel roleModel : roles) { if (client.hasScope(roleModel)) continue; @@ -153,10 +155,10 @@ public class ScopeMappedResource { auth.requireView(); Set roles = realm.getRoles(); - return getComposite(client, roles); + return getComposite(scopeContainer, roles); } - public static List getComposite(ClientModel client, Set roles) { + public static List getComposite(ScopeContainerModel client, Set roles) { List composite = new ArrayList(); for (RoleModel roleModel : roles) { if (client.hasScope(roleModel)) composite.add(ModelToRepresentation.toRepresentation(roleModel)); @@ -180,7 +182,7 @@ public class ScopeMappedResource { if (roleModel == null) { throw new NotFoundException("Role not found"); } - client.addScopeMapping(roleModel); + scopeContainer.addScopeMapping(roleModel); adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), role.getId()).representation(roles).success(); } } @@ -197,9 +199,9 @@ public class ScopeMappedResource { auth.requireManage(); if (roles == null) { - Set roleModels = client.getRealmScopeMappings(); + Set roleModels = scopeContainer.getRealmScopeMappings(); for (RoleModel roleModel : roleModels) { - client.deleteScopeMapping(roleModel); + scopeContainer.deleteScopeMapping(roleModel); } adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri()).representation(roles).success(); } else { @@ -208,7 +210,7 @@ public class ScopeMappedResource { if (roleModel == null) { throw new NotFoundException("Client not found"); } - client.deleteScopeMapping(roleModel); + scopeContainer.deleteScopeMapping(roleModel); adminEvent.operation(OperationType.DELETE).resourcePath(session.getContext().getUri(), roleModel.getId()).representation(roles).success(); } } @@ -221,6 +223,6 @@ public class ScopeMappedResource { if (clientModel == null) { throw new NotFoundException("Client not found"); } - return new ScopeMappedClientResource(realm, auth, this.client, session, clientModel, adminEvent); + return new ScopeMappedClientResource(realm, auth, this.scopeContainer, session, clientModel, adminEvent); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java index d02c2559cc..ab76f89941 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -168,7 +168,7 @@ public class AccountTest { }); } - //@Test + @Test public void ideTesting() throws Exception { Thread.sleep(100000000); } 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 f016ff09f1..57afc834b1 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 @@ -25,6 +25,7 @@ import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper; @@ -191,7 +192,7 @@ public class ImportTest extends AbstractModelTest { Set realmScopes = oauthClient.getRealmScopeMappings(); Assert.assertTrue(realmScopes.contains(realm.getRole("admin"))); - Set appScopes = application.getClientScopeMappings(oauthClient); + Set appScopes = KeycloakModelUtils.getClientScopeMappings(application, oauthClient);//application.getClientScopeMappings(oauthClient); Assert.assertTrue(appScopes.contains(application.getRole("app-user"))); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java index 8473540d73..5977916922 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java @@ -62,6 +62,8 @@ import org.keycloak.representations.IDToken; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientTemplateRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.OAuthClient; @@ -803,6 +805,25 @@ public class AccessTokenTest { @Test public void testClientTemplate() throws Exception { RealmResource realm = keycloak.realms().realm("test"); + RoleRepresentation realmRole = new RoleRepresentation(); + realmRole.setName("realm-test-role"); + realm.roles().create(realmRole); + realmRole = realm.roles().get("realm-test-role").toRepresentation(); + RoleRepresentation realmRole2 = new RoleRepresentation(); + realmRole2.setName("realm-test-role2"); + realm.roles().create(realmRole2); + realmRole2 = realm.roles().get("realm-test-role2").toRepresentation(); + + + List users = realm.users().search("test-user@localhost", -1, -1); + Assert.assertEquals(1, users.size()); + UserRepresentation user = users.get(0); + + List addRoles = new LinkedList<>(); + addRoles.add(realmRole); + addRoles.add(realmRole2); + realm.users().get(user.getId()).roles().realmLevel().add(addRoles); + ClientTemplateRepresentation rep = new ClientTemplateRepresentation(); rep.setName("template"); rep.setProtocol("oidc"); @@ -825,8 +846,8 @@ public class AccessTokenTest { } } - Assert.assertNotNull(clientRep); clientRep.setClientTemplate("template"); + clientRep.setFullScopeAllowed(false); realm.clients().get(clientRep.getId()).update(clientRep); { @@ -844,13 +865,150 @@ public class AccessTokenTest { AccessToken accessToken = getAccessToken(tokenResponse); Assert.assertEquals("coded", accessToken.getOtherClaims().get("hard")); + // check zero scope for template + Assert.assertFalse(accessToken.getRealmAccess().getRoles().contains(realmRole.getName())); + Assert.assertFalse(accessToken.getRealmAccess().getRoles().contains(realmRole2.getName())); + response.close(); client.close(); } + + // test that scope is added + List addRole1 = new LinkedList<>(); + addRole1.add(realmRole); + templateResource.getScopeMappings().realmLevel().add(addRole1); + + { + Client client = ClientBuilder.newClient(); + UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); + WebTarget grantTarget = client.target(grantUri); + + response = executeGrantAccessTokenRequest(grantTarget); + Assert.assertEquals(200, response.getStatus()); + org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class); + AccessToken accessToken = getAccessToken(tokenResponse); + // check zero scope for template + Assert.assertNotNull(accessToken.getRealmAccess()); + Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains(realmRole.getName())); + Assert.assertFalse(accessToken.getRealmAccess().getRoles().contains(realmRole2.getName())); + + + response.close(); + client.close(); + } + + // test combined scopes + List addRole2 = new LinkedList<>(); + addRole2.add(realmRole2); + realm.clients().get(clientRep.getId()).getScopeMappings().realmLevel().add(addRole2); + + { + Client client = ClientBuilder.newClient(); + UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); + WebTarget grantTarget = client.target(grantUri); + + response = executeGrantAccessTokenRequest(grantTarget); + Assert.assertEquals(200, response.getStatus()); + org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class); + + AccessToken accessToken = getAccessToken(tokenResponse); + + // check zero scope for template + Assert.assertNotNull(accessToken.getRealmAccess()); + Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains(realmRole.getName())); + Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains(realmRole2.getName())); + + + response.close(); + client.close(); + } + + // remove scopes and retest + templateResource.getScopeMappings().realmLevel().remove(addRole1); + realm.clients().get(clientRep.getId()).getScopeMappings().realmLevel().remove(addRole2); + + { + Client client = ClientBuilder.newClient(); + UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); + WebTarget grantTarget = client.target(grantUri); + + response = executeGrantAccessTokenRequest(grantTarget); + Assert.assertEquals(200, response.getStatus()); + org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class); + + AccessToken accessToken = getAccessToken(tokenResponse); + Assert.assertFalse(accessToken.getRealmAccess().getRoles().contains(realmRole.getName())); + Assert.assertFalse(accessToken.getRealmAccess().getRoles().contains(realmRole2.getName())); + + + response.close(); + client.close(); + } + + // test full scope on template + rep.setFullScopeAllowed(true); + templateResource.update(rep); + + { + Client client = ClientBuilder.newClient(); + UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); + WebTarget grantTarget = client.target(grantUri); + + response = executeGrantAccessTokenRequest(grantTarget); + Assert.assertEquals(200, response.getStatus()); + org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class); + + AccessToken accessToken = getAccessToken(tokenResponse); + + // check zero scope for template + Assert.assertNotNull(accessToken.getRealmAccess()); + Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains(realmRole.getName())); + Assert.assertTrue(accessToken.getRealmAccess().getRoles().contains(realmRole2.getName())); + + + response.close(); + client.close(); + } + + // test don't use template scope + clientRep.setUseTemplateScope(false); + realm.clients().get(clientRep.getId()).update(clientRep); + + { + Client client = ClientBuilder.newClient(); + UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); + WebTarget grantTarget = client.target(grantUri); + + response = executeGrantAccessTokenRequest(grantTarget); + Assert.assertEquals(200, response.getStatus()); + org.keycloak.representations.AccessTokenResponse tokenResponse = response.readEntity(org.keycloak.representations.AccessTokenResponse.class); + + AccessToken accessToken = getAccessToken(tokenResponse); + Assert.assertFalse(accessToken.getRealmAccess().getRoles().contains(realmRole.getName())); + Assert.assertFalse(accessToken.getRealmAccess().getRoles().contains(realmRole2.getName())); + + + response.close(); + client.close(); + } + + + + // undo mappers clientRep.setClientTemplate(ClientTemplateRepresentation.NONE); + clientRep.setFullScopeAllowed(true); realm.clients().get(clientRep.getId()).update(clientRep); + realm.users().get(user.getId()).roles().realmLevel().remove(addRoles); + realm.roles().get(realmRole.getName()).remove(); + realm.roles().get(realmRole2.getName()).remove(); + templateResource.remove(); { Client client = ClientBuilder.newClient();