keycloak-aplcache
Changes
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js 128(+125 -3)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html 15(+14 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-scope-mappings.html 26(+23 -3)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-scope-mappings.html 117(+117 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client-template.html 4(+4 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientTemplateResource.java 5(+5 -0)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java 60(+41 -19)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientTemplateAdapter.java 64(+64 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java 18(+18 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClientTemplate.java 14(+14 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java 51(+38 -13)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientTemplateAdapter.java 63(+63 -0)
Details
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index 36f33bf..b2af448 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 @@
<class>org.keycloak.models.jpa.entities.GroupRoleMappingEntity</class>
<class>org.keycloak.models.jpa.entities.UserGroupMembershipEntity</class>
<class>org.keycloak.models.jpa.entities.ClientTemplateEntity</class>
+ <class>org.keycloak.models.jpa.entities.TemplateScopeMappingEntity</class>
<!-- JpaAuditProviders -->
<class>org.keycloak.events.jpa.EventEntity</class>
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 db2a791..068d4e7 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 @@
<column name="REALM_ID" type="VARCHAR(36)"/>
<column name="DESCRIPTION" type="VARCHAR(255)"/>
<column name="PROTOCOL" type="VARCHAR(255)"/>
+ <column name="FULL_SCOPE_ALLOWED" type="BOOLEAN" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ <createTable tableName="TEMPLATE_SCOPE_MAPPING">
+ <column name="TEMPLATE_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="ROLE_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
</createTable>
@@ -24,12 +35,21 @@
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
+ <column name="USE_TEMPLATE_CONFIG" type="BOOLEAN" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ <column name="USE_TEMPLATE_SCOPE" type="BOOLEAN" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ <column name="USE_TEMPLATE_MAPPERS" type="BOOLEAN" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
</addColumn>
<addColumn tableName="PROTOCOL_MAPPER">
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
- </addColumn>
+ </addColumn>
<createTable tableName="REALM_CLIENT_TEMPLATE">
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
@@ -46,6 +66,9 @@
<addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="CLIENT" constraintName="FK_CLI_TMPLT_CLIENT" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_RLM" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_CLI" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
+ <addPrimaryKey columnNames="TEMPLATE_ID, ROLE_ID" constraintName="PK_TEMPLATE_SCOPE" tableName="TEMPLATE_SCOPE_MAPPING"/>
+ <addForeignKeyConstraint baseColumnNames="TEMPLATE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_TEMPL" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
+ <addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
</changeSet>
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 2384a2e..ab81db5 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<String, Integer> registeredNodes;
protected List<ProtocolMapperRepresentation> 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 f0bf09e..dc575c4 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<ProtocolMapperRepresentation> 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 f7a3c25..cebb3d4 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 a6a9130..ec28ef4 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 7a47a83..b0d9567 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 9b98065..86b52e1 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 @@
<kc-tabs-client></kc-tabs-client>
+ <form class="form-horizontal" name="allowScope" novalidate kc-read-only="!access.manageClients">
+ <fieldset class="border-top">
+ <div class="form-group" ng-show="client.clientTemplate">
+ <label class="col-md-2 control-label" for="useTemplateScope">Inherit Template Mappers</label>
+ <kc-tooltip>Inherit mappers from client template</kc-tooltip>
+ <div class="col-md-1">
+ <input ng-model="client.useTemplateMappers" ng-click="changeFlag()" name="useTemplateScope" id="useTemplateScope" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <div class="col-md-2">
+ <a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers">view template mappers</a>
+ </div>
+ </div>
+ </fieldset>
+ </form>
<table class="table table-striped table-bordered">
<thead>
<tr>
@@ -24,7 +38,6 @@
<div class="pull-right" data-ng-show="access.manageClients">
<a class="btn btn-default" href="#/create/client/{{realm.realm}}/{{client.id}}/mappers">{{:: 'create' | translate}}</a>
<a class="btn btn-default" href="#/realms/{{realm.realm}}/clients/{{client.id}}/add-mappers">{{:: 'add-builtin' | translate}}</a>
- <a ng-show="template" class="btn btn-default" href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers">Inherited Template Mappers</a>
</div>
</div>
</th>
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 cd63f49..8075065 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 @@
<p class="subtitle"></p>
<form class="form-horizontal" name="allowScope" novalidate kc-read-only="!access.manageClients">
<fieldset class="border-top">
- <div class="form-group">
+ <div class="form-group" ng-show="client.clientTemplate">
+ <label class="col-md-2 control-label" for="useTemplateScope">Inherit Template Scope</label>
+ <kc-tooltip>Inherit scope from client template</kc-tooltip>
+ <div class="col-md-1">
+ <input ng-model="client.useTemplateScope" ng-click="changeFlag()" name="useTemplateScope" id="useTemplateScope" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <div class="col-md-2">
+ <a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/scope-mappings">view template scope</a>
+ </div>
+ </div>
+ <div class="form-group" ng-hide="client.useTemplateScope && template && template.fullScopeAllowed">
<label class="col-md-2 control-label" for="fullScopeAllowed">{{:: 'full-scope-allowed' | translate}}</label>
<kc-tooltip>{{:: 'full-scope-allowed.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
- <input ng-model="client.fullScopeAllowed" ng-click="changeFullScopeAllowed()" name="fullScopeAllowed" id="fullScopeAllowed" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ <input ng-model="client.fullScopeAllowed" ng-click="changeFlag()" name="fullScopeAllowed" id="fullScopeAllowed" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ </div>
+ <div class="form-group" ng-show="client.useTemplateScope && template && template.fullScopeAllowed">
+ <label class="col-md-2 control-label" for="fullScopeAllowed">{{:: 'full-scope-allowed' | translate}}</label>
+ <kc-tooltip>Client template has full scope allowed, which means this client will have the full scope of all roles.</kc-tooltip>
+ <div class="col-md-1">
+ <input ng-model="template.fullScopeAllowed" name="fullScopeAllowed" id="fullScopeAllowed" ng-disabled="true" onoffswitch />
+ </div>
+ <div class="col-md-1">
+ <i>inherited</i>
</div>
</div>
</fieldset>
</form>
- <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients" data-ng-show="!client.fullScopeAllowed">
+ <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients" data-ng-show="!client.fullScopeAllowed" data-ng-hide="client.useTemplateScope && template && template.fullScopeAllowed">
<div class="form-group">
<label class="col-md-2 control-label" class="control-label">{{:: 'realm-roles' | translate}}</label>
<div class="col-md-10">
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 0000000..6903071
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-template-scope-mappings.html
@@ -0,0 +1,117 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/client-templates">Client Templates</a></li>
+ <li>{{template.name}}</li>
+ </ol>
+
+ <kc-tabs-client-template></kc-tabs-client-template>
+
+ <h2><span>{{template.name}}</span> {{:: 'scope-mappings' | translate}} </h2>
+ <p class="subtitle"></p>
+ <form class="form-horizontal" name="allowScope" novalidate kc-read-only="!access.manageClients">
+ <fieldset class="border-top">
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="fullScopeAllowed">{{:: 'full-scope-allowed' | translate}}</label>
+ <kc-tooltip>{{:: 'full-scope-allowed.tooltip' | translate}}</kc-tooltip>
+ <div class="col-md-6">
+ <input ng-model="template.fullScopeAllowed" ng-click="changeFullScopeAllowed()" name="fullScopeAllowed" id="fullScopeAllowed" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ </div>
+ </fieldset>
+ </form>
+
+ <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients" data-ng-show="!template.fullScopeAllowed">
+ <div class="form-group">
+ <label class="col-md-2 control-label" class="control-label">{{:: 'realm-roles' | translate}}</label>
+ <div class="col-md-10">
+ <div class="row">
+ <div class="col-md-3">
+ <label class="control-label" for="available">{{:: 'available-roles' | translate}}</label>
+ <kc-tooltip>{{:: 'scope.available-roles.tooltip' | translate}}</kc-tooltip>
+
+ <select id="available" class="form-control" multiple size="5"
+ ng-multiple="true"
+ ng-model="selectedRealmRoles"
+ ng-options="r.name for r in realmRoles">
+ </select>
+ <button ng-disabled="selectedRealmRoles.length == 0" class="btn btn-default" type="submit" ng-click="addRealmRole()">
+ {{:: 'add-selected' | translate}} <i class="fa fa-angle-double-right"></i>
+ </button>
+ </div>
+ <div class="col-md-3">
+ <label class="control-label" for="assigned">{{:: 'assigned-roles' | translate}}</label>
+ <kc-tooltip>{{:: 'assigned-roles.tooltip' | translate}}</kc-tooltip>
+ <select id="assigned" class="form-control" multiple size=5
+ ng-multiple="true"
+ ng-model="selectedRealmMappings"
+ ng-options="r.name for r in realmMappings">
+ </select>
+ <button ng-disabled="selectedRealmMappings.length == 0" class="btn btn-default" type="submit" ng-click="deleteRealmRole()">
+ <i class="fa fa-angle-double-left"></i> {{:: 'remove-selected' | translate}}
+ </button>
+ </div>
+ <div class="col-md-3">
+ <label class="control-label" for="realm-composite">{{:: 'effective-roles' | translate}} </label>
+ <kc-tooltip>{{:: 'realm.effective-roles.tooltip' | translate}}</kc-tooltip>
+ <select id="realm-composite" class="form-control" multiple size=5
+ disabled="true"
+ ng-model="dummymodel"
+ ng-options="r.name for r in realmComposite">
+ </select>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label class="col-md-2 control-label" class="control-label">
+ <span>{{:: 'client-roles' | translate}}</span>
+ <select class="form-control" id="clients" name="clients" ng-change="changeClient()" ng-model="targetClient" ng-options="a.clientId for a in clients" ng-disabled="false"></select>
+ </label>
+
+ <div class="col-md-10">
+ <div class="row" data-ng-hide="targetClient">
+ <div class="col-md-4"><span class="text-muted">{{:: 'select-client-roles.tooltip' | translate}}</span></div>
+ </div>
+ <div class="row" data-ng-show="targetClient">
+ <div class="col-md-3">
+ <label class="control-label" for="available-client">{{:: 'available-roles' | translate}}</label>
+ <kc-tooltip>{{:: 'assign.available-roles.tooltip' | translate}}</kc-tooltip>
+ <select id="available-client" class="form-control" multiple size="5"
+ ng-multiple="true"
+ ng-model="selectedClientRoles"
+ ng-options="r.name for r in clientRoles">
+ </select>
+ <button ng-disabled="selectedClientRoles.length == 0" class="btn btn-default" type="submit" ng-click="addClientRole()">
+ {{:: 'add-selected' | translate}} <i class="fa fa-angle-double-right"></i>
+ </button>
+ </div>
+ <div class="col-md-3">
+ <label class="control-label" for="assigned-client">{{:: 'assigned-roles' | translate}}</label>
+ <kc-tooltip>{{:: 'client.assigned-roles.tooltip' | translate}}</kc-tooltip>
+ <select id="assigned-client" class="form-control" multiple size=5
+ ng-multiple="true"
+ ng-model="selectedClientMappings"
+ ng-options="r.name for r in clientMappings">
+ </select>
+ <button ng-disabled="selectedClientMappings.length == 0" class="btn btn-default" type="submit" ng-click="deleteClientRole()">
+ <i class="fa fa-angle-double-left"></i> {{:: 'remove-selected' | translate}}
+ </button>
+ </div>
+ <div class="col-md-3">
+ <label class="control-label" for="client-composite">{{:: 'effective-roles' | translate}}</label>
+ <kc-tooltip>{{:: 'client.effective-roles.tooltip' | translate}}</kc-tooltip>
+ <select id="client-composite" class="form-control" multiple size=5
+ disabled="true"
+ ng-model="dummymodel"
+ ng-options="r.name for r in clientComposite">
+ </select>
+ </div>
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
+
+<kc-menu></kc-menu>
\ 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 728e091..0bbdaf0 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 @@
<a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers">{{:: 'mappers' | translate}}</a>
<kc-tooltip>{{:: 'mappers.tooltip' | translate}}</kc-tooltip>
</li>
+ <li ng-class="{active: path[4] == 'scope-mappings'}" >
+ <a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/scope-mappings">{{:: 'scope' | translate}}</a>
+ <kc-tooltip>{{:: 'scope.tooltip' | translate}}</kc-tooltip>
+ </li>
</ul>
</div>
\ 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 0a5d8ff..a5665cd 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 f7e0305..475edf2 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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<RoleModel> 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<RoleModel> getScopeMappings();
- void addScopeMapping(RoleModel role);
- void deleteScopeMapping(RoleModel role);
- Set<RoleModel> 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 f7d67ac..f3c0f59 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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index 24e5fc6..04daf9f 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<ClientIdentityProviderMappingEntity> identityProviders = new ArrayList<ClientIdentityProviderMappingEntity>();
private List<ProtocolMapperEntity> protocolMappers = new ArrayList<ProtocolMapperEntity>();
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 849eaa1..8ca932f 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<String> scopeIds = new ArrayList<String>();
private List<ProtocolMapperEntity> protocolMappers = new ArrayList<ProtocolMapperEntity>();
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<String> getScopeIds() {
+ return scopeIds;
+ }
+
+ public void setScopeIds(List<String> 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 8c6af88..f08eee4 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<RoleModel> getClientScopeMappings(ClientModel client, ScopeContainerModel container) {
+ Set<RoleModel> mappings = container.getScopeMappings();
+ Set<RoleModel> 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 7b6a198..e891582 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 4c7dad1..2d99c3b 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<ProtocolMapperModel> 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 d96e6bc..164fc74 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);
@@ -413,25 +454,6 @@ public class ClientAdapter implements ClientModel {
}
@Override
- public Set<RoleModel> getClientScopeMappings(ClientModel client) {
- Set<RoleModel> roleMappings = client.getScopeMappings();
-
- Set<RoleModel> appRoles = new HashSet<RoleModel>();
- 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();
return cached.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 2a1674d..13b68aa 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
@@ -133,6 +133,70 @@ public class ClientTemplateAdapter implements ClientTemplateModel {
}
@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<RoleModel> getScopeMappings() {
+ if (updated != null) return updated.getScopeMappings();
+ Set<RoleModel> roles = new HashSet<RoleModel>();
+ 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<RoleModel> getRealmScopeMappings() {
+ Set<RoleModel> roleMappings = getScopeMappings();
+
+ Set<RoleModel> appRoles = new HashSet<RoleModel>();
+ 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<RoleModel> 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;
if (o == null || !(o instanceof ClientModel)) return false;
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 dbf4754..ab83077 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<String, Integer> 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 2df28c1..58bcc12 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<String> scope = new HashSet<String>();
private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
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<String> 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 a8abd11..0d9eea4 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<RoleModel> getRealmScopeMappings() {
+ public Set<RoleModel> getRealmScopeMappings() {
Set<RoleModel> roleMappings = getScopeMappings();
Set<RoleModel> appRoles = new HashSet<>();
@@ -238,7 +238,8 @@ public class ClientAdapter implements ClientModel {
@Override
public void addScopeMapping(RoleModel role) {
- if (hasScope(role)) return;
+ Set<RoleModel> 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);
@@ -642,28 +677,6 @@ public class ClientAdapter implements ClientModel {
}
@Override
- public Set<RoleModel> getClientScopeMappings(ClientModel client) {
- Set<RoleModel> roleMappings = client.getScopeMappings();
-
- Set<RoleModel> appRoles = new HashSet<RoleModel>();
- 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<String> getDefaultRoles() {
Collection<RoleEntity> entities = entity.getDefaultRoles();
List<String> roles = new ArrayList<String>();
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 8e92e79..2d5c78b 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<RoleModel> getRealmScopeMappings() {
+ Set<RoleModel> roleMappings = getScopeMappings();
+
+ Set<RoleModel> 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<RoleModel> getScopeMappings() {
+ TypedQuery<String> query = em.createNamedQuery("clientTemplateScopeMappingIds", String.class);
+ query.setParameter("template", getEntity());
+ List<String> ids = query.getResultList();
+ Set<RoleModel> roles = new HashSet<RoleModel>();
+ 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<TemplateScopeMappingEntity> query = getRealmScopeMappingQuery(role);
+ List<TemplateScopeMappingEntity> results = query.getResultList();
+ if (results.size() == 0) return;
+ for (TemplateScopeMappingEntity entity : results) {
+ em.remove(entity);
+ }
+ }
+
+ protected TypedQuery<TemplateScopeMappingEntity> getRealmScopeMappingQuery(RoleModel role) {
+ TypedQuery<TemplateScopeMappingEntity> 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<RoleModel> 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/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 6767e7b..80781b8 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 ff4bd14..5da01c4 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 0000000..375fd05
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @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/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 25df437..25e79f8 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 4f96829..ccc3339 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/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 af448c4..02ccea4 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;
@@ -621,19 +622,6 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
}
@Override
- public Set<RoleModel> getClientScopeMappings(ClientModel client) {
- Set<RoleModel> result = new HashSet<RoleModel>();
- List<MongoRoleEntity> 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<String> getDefaultRoles() {
return getMongoEntity().getDefaultRoles();
}
@@ -726,4 +714,41 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> 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 ee19dea..91725bf 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<MongoClientTempl
return mapping;
}
+ @Override
+ public boolean isFullScopeAllowed() {
+ return getMongoEntity().isFullScopeAllowed();
+ }
+
+ @Override
+ public void setFullScopeAllowed(boolean value) {
+ getMongoEntity().setFullScopeAllowed(value);
+ updateMongoEntity();
+
+ }
+ @Override
+ public Set<RoleModel> getScopeMappings() {
+ Set<RoleModel> result = new HashSet<RoleModel>();
+ List<MongoRoleEntity> 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<RoleModel> getRealmScopeMappings() {
+ Set<RoleModel> 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<RoleModel> realmRoles = new HashSet<RoleModel>();
+ 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<RoleModel> 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 eaf2216..907b29d 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;
@@ -57,4 +60,17 @@ public class MongoModelUtils {
.get();
return invContext.getMongoStore().loadEntities(MongoRoleEntity.class, query, invContext);
}
+ public static List<MongoRoleEntity> getAllScopesOfTemplate(ClientTemplateModel template, MongoStoreInvocationContext invContext) {
+ ClientTemplateEntity scopedEntity = ((ClientTemplateAdapter)template).getMongoEntity();
+ List<String> scopeIds = scopedEntity.getScopeIds();
+
+ if (scopeIds == null || scopeIds.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ DBObject query = new QueryBuilder()
+ .and("_id").in(scopeIds)
+ .get();
+ return invContext.getMongoStore().loadEntities(MongoRoleEntity.class, query, invContext);
+ }
}
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 346832a..d1a83bb 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<String> requestedProtocolMappers = new HashSet<String>();
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<RoleModel> scopeMappings = client.getScopeMappings();
+ Set<RoleModel> scopeMappings = new HashSet<>();
+ if (useTemplateScope) {
+ logger.debug("Adding template scope mappings");
+ scopeMappings.addAll(template.getScopeMappings());
+ }
scopeMappings.addAll(client.getRoles());
-
+ Set<RoleModel> clientScopeMappings = client.getScopeMappings();
+ scopeMappings.addAll(clientScopeMappings);
for (RoleModel role : roleMappings) {
for (RoleModel desiredRole : scopeMappings) {
Set<RoleModel> visited = new HashSet<RoleModel>();
@@ -337,7 +345,6 @@ public class TokenManager {
}
}
}
-
if (applyScopeParam) {
Collection<String> 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 509d485..53cc76b 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,12 +92,22 @@ 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
* @return
@@ -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 44b355e..b90cb0f 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<RoleRepresentation> getClientScopeMappings() {
auth.requireView();
- Set<RoleModel> mappings = scopedClient.getClientScopeMappings(client);
+ Set<RoleModel> mappings = KeycloakModelUtils.getClientScopeMappings(scopedClient, scopeContainer); //scopedClient.getClientScopeMappings(client);
List<RoleRepresentation> mapRep = new ArrayList<RoleRepresentation>();
for (RoleModel roleModel : mappings) {
mapRep.add(ModelToRepresentation.toRepresentation(roleModel));
@@ -79,7 +81,7 @@ public class ScopeMappedClientResource {
auth.requireView();
Set<RoleModel> roles = scopedClient.getRoles();
- return ScopeMappedResource.getAvailable(client, roles);
+ return ScopeMappedResource.getAvailable(scopeContainer, roles);
}
/**
@@ -97,7 +99,7 @@ public class ScopeMappedClientResource {
auth.requireView();
Set<RoleModel> 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<RoleModel> roleModels = scopedClient.getClientScopeMappings(client);
+ Set<RoleModel> 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 2d9b6a2..499392c 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<RoleModel> realmMappings = client.getRealmScopeMappings();
+ Set<RoleModel> realmMappings = scopeContainer.getRealmScopeMappings();
if (realmMappings.size() > 0) {
List<RoleRepresentation> realmRep = new ArrayList<RoleRepresentation>();
for (RoleModel roleModel : realmMappings) {
@@ -73,7 +75,7 @@ public class ScopeMappedResource {
if (clients.size() > 0) {
Map<String, ClientMappingsRepresentation> clientMappings = new HashMap<String, ClientMappingsRepresentation>();
for (ClientModel client : clients) {
- Set<RoleModel> roleMappings = client.getClientScopeMappings(this.client);
+ Set<RoleModel> 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<RoleRepresentation> getRealmScopeMappings() {
auth.requireView();
- Set<RoleModel> realmMappings = client.getRealmScopeMappings();
+ Set<RoleModel> realmMappings = scopeContainer.getRealmScopeMappings();
List<RoleRepresentation> realmMappingsRep = new ArrayList<RoleRepresentation>();
for (RoleModel roleModel : realmMappings) {
realmMappingsRep.add(ModelToRepresentation.toRepresentation(roleModel));
@@ -124,10 +126,10 @@ public class ScopeMappedResource {
auth.requireView();
Set<RoleModel> roles = realm.getRoles();
- return getAvailable(client, roles);
+ return getAvailable(scopeContainer, roles);
}
- public static List<RoleRepresentation> getAvailable(ClientModel client, Set<RoleModel> roles) {
+ public static List<RoleRepresentation> getAvailable(ScopeContainerModel client, Set<RoleModel> roles) {
List<RoleRepresentation> available = new ArrayList<RoleRepresentation>();
for (RoleModel roleModel : roles) {
if (client.hasScope(roleModel)) continue;
@@ -153,10 +155,10 @@ public class ScopeMappedResource {
auth.requireView();
Set<RoleModel> roles = realm.getRoles();
- return getComposite(client, roles);
+ return getComposite(scopeContainer, roles);
}
- public static List<RoleRepresentation> getComposite(ClientModel client, Set<RoleModel> roles) {
+ public static List<RoleRepresentation> getComposite(ScopeContainerModel client, Set<RoleModel> roles) {
List<RoleRepresentation> composite = new ArrayList<RoleRepresentation>();
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<RoleModel> roleModels = client.getRealmScopeMappings();
+ Set<RoleModel> 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 d02c255..ab76f89 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 f016ff0..57afc83 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<RoleModel> realmScopes = oauthClient.getRealmScopeMappings();
Assert.assertTrue(realmScopes.contains(realm.getRole("admin")));
- Set<RoleModel> appScopes = application.getClientScopeMappings(oauthClient);
+ Set<RoleModel> 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 8473540..5977916 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<UserRepresentation> users = realm.users().search("test-user@localhost", -1, -1);
+ Assert.assertEquals(1, users.size());
+ UserRepresentation user = users.get(0);
+
+ List<RoleRepresentation> 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<RoleRepresentation> 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<RoleRepresentation> 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();