From b45d6b896a226f6179206aec4b67dfe53094498e Mon Sep 17 00:00:00 2001 From: pedroigor Date: Fri, 27 Feb 2015 02:13:27 -0300 Subject: [PATCH] [KEYCLOAK-883] - Configuration option to disable token retrieval from applications. --- .../META-INF/jpa-changelog-1.2.0.Beta1.xml | 10 +- .../main/resources/META-INF/persistence.xml | 1 + .../idm/ApplicationRepresentation.java | 11 +- ...IdentityProviderMappingRepresentation.java | 43 +++++++ .../idm/OAuthClientRepresentation.java | 11 +- .../en/en-US/modules/identity-broker.xml | 6 +- .../resources/js/controllers/applications.js | 78 ++++++----- .../resources/js/controllers/oauth-clients.js | 78 ++++++----- .../application-identity-provider.html | 17 ++- .../oauth-client-identity-provider.html | 15 ++- .../ClientIdentityProviderMappingModel.java | 43 +++++++ .../java/org/keycloak/models/ClientModel.java | 7 +- .../models/entities/ClientEntity.java | 10 +- .../ClientIdentityProviderMappingEntity.java | 44 +++++++ .../models/utils/ModelToRepresentation.java | 25 +++- .../models/utils/RepresentationToModel.java | 78 +++++++---- .../keycloak/models/cache/ClientAdapter.java | 15 ++- .../models/cache/entities/CachedClient.java | 27 +++- .../keycloak/models/jpa/ClientAdapter.java | 94 ++++++++++---- .../models/jpa/entities/ClientEntity.java | 13 +- .../ClientIdentityProviderMappingEntity.java | 121 ++++++++++++++++++ .../keycloak/adapters/ClientAdapter.java | 54 ++++++-- .../resources/IdentityBrokerService.java | 4 + .../admin/IdentityProviderResource.java | 12 +- .../admin/IdentityProvidersResource.java | 8 +- .../broker/AbstractIdentityProviderTest.java | 49 ++++++- .../broker/ImportIdentityProviderTest.java | 27 ++++ .../broker-test/test-realm-with-broker.json | 7 +- 28 files changed, 725 insertions(+), 183 deletions(-) create mode 100644 core/src/main/java/org/keycloak/representations/idm/ClientIdentityProviderMappingRepresentation.java create mode 100644 model/api/src/main/java/org/keycloak/models/ClientIdentityProviderMappingModel.java create mode 100644 model/api/src/main/java/org/keycloak/models/entities/ClientIdentityProviderMappingEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientIdentityProviderMappingEntity.java diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml index 6139c3d931f3..b27e9bd616fd 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml @@ -73,13 +73,14 @@ - + - + + @@ -104,10 +105,11 @@ - - + + + diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml index 8609f07a5e09..eff01c4ce483 100755 --- a/connections/jpa/src/main/resources/META-INF/persistence.xml +++ b/connections/jpa/src/main/resources/META-INF/persistence.xml @@ -18,6 +18,7 @@ org.keycloak.models.jpa.entities.UserRoleMappingEntity org.keycloak.models.jpa.entities.ScopeMappingEntity org.keycloak.models.jpa.entities.IdentityProviderEntity + org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity org.keycloak.models.jpa.entities.ClaimTypeEntity org.keycloak.models.jpa.entities.ProtocolMapperEntity diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java index 9ca74f07d0fe..6537fc25df15 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java @@ -2,7 +2,6 @@ import java.util.List; import java.util.Map; -import java.util.Set; /** * @author Bill Burke @@ -29,7 +28,7 @@ public class ApplicationRepresentation { protected Boolean fullScopeAllowed; protected Integer nodeReRegistrationTimeout; protected Map registeredNodes; - protected List allowedIdentityProviders; + protected List identityProviders; protected List protocolMappers; public String getId() { @@ -192,12 +191,12 @@ public void setFrontchannelLogout(Boolean frontchannelLogout) { this.frontchannelLogout = frontchannelLogout; } - public List getAllowedIdentityProviders() { - return this.allowedIdentityProviders; + public List getIdentityProviders() { + return this.identityProviders; } - public void setAllowedIdentityProviders(List allowedIdentityProviders) { - this.allowedIdentityProviders = allowedIdentityProviders; + public void setIdentityProviders(List identityProviders) { + this.identityProviders = identityProviders; } public List getProtocolMappers() { diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientIdentityProviderMappingRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientIdentityProviderMappingRepresentation.java new file mode 100644 index 000000000000..fdc02d3117bf --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ClientIdentityProviderMappingRepresentation.java @@ -0,0 +1,43 @@ +/* + * JBoss, Home of Professional Open Source + * + * Copyright 2013 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.representations.idm; + +/** + * @author pedroigor + */ +public class ClientIdentityProviderMappingRepresentation { + + protected String id; + protected boolean retrieveToken; + + public String getId() { + return this.id; + } + + public void setId(String identityProviderId) { + this.id = identityProviderId; + } + + public boolean isRetrieveToken() { + return this.retrieveToken; + } + + public void setRetrieveToken(boolean retrieveToken) { + this.retrieveToken = retrieveToken; + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java index aa095a7000ae..c12a9ebe608a 100755 --- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java @@ -2,7 +2,6 @@ import java.util.List; import java.util.Map; -import java.util.Set; /** * @author Bill Burke @@ -23,8 +22,8 @@ public class OAuthClientRepresentation { protected Boolean directGrantsOnly; protected Boolean fullScopeAllowed; protected Boolean frontchannelLogout; - protected List allowedIdentityProviders; protected List protocolMappers; + private List identityProviders; public String getId() { @@ -139,12 +138,12 @@ public void setFrontchannelLogout(Boolean frontchannelLogout) { this.frontchannelLogout = frontchannelLogout; } - public List getAllowedIdentityProviders() { - return this.allowedIdentityProviders; + public List getIdentityProviders() { + return this.identityProviders; } - public void setAllowedIdentityProviders(List allowedIdentityProviders) { - this.allowedIdentityProviders = allowedIdentityProviders; + public void setIdentityProviders(List identityProviders) { + this.identityProviders = identityProviders; } public List getProtocolMappers() { diff --git a/docbook/reference/en/en-US/modules/identity-broker.xml b/docbook/reference/en/en-US/modules/identity-broker.xml index 6ccc6303c5f3..233afed979cf 100755 --- a/docbook/reference/en/en-US/modules/identity-broker.xml +++ b/docbook/reference/en/en-US/modules/identity-broker.xml @@ -962,7 +962,7 @@ Authorization: Bearer {keycloak_access_token}]]>
- Enabling/Disabling Identity Providers for Service Providers + Configuring Identity Providers for Applications By default, all identity providers enabled for a particular realm are also available to all its applications. However, you can also specify which identity providers should be available when @@ -993,6 +993,10 @@ Authorization: Bearer {keycloak_access_token}]]> + + From this page you can also configure if an application is allowed to retrieve tokens from an specific identity provider. For that, + just click on the Can Retrieve Token button. +
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js index 617d5abbd156..3e758c28b380 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js @@ -43,75 +43,95 @@ module.controller('ApplicationCredentialsCtrl', function($scope, $location, real }); }); -module.controller('ApplicationIdentityProviderCtrl', function($scope, $location, realm, application, Application, $location, Notifications) { +module.controller('ApplicationIdentityProviderCtrl', function($scope, $location, $route, realm, application, Application, $location, Notifications) { $scope.realm = realm; $scope.application = angular.copy(application); + var length = 0; - $scope.identityProviders = []; + if ($scope.application.identityProviders) { + length = $scope.application.identityProviders.length; + } else { + $scope.application.identityProviders = new Array(realm.identityProviders.length); + } - if (!$scope.application.allowedIdentityProviders) { - $scope.application.allowedIdentityProviders = []; + for (j = length; j < realm.identityProviders.length; j++) { + $scope.application.identityProviders[j] = {}; } + $scope.identityProviders = []; + for (j = 0; j < realm.identityProviders.length; j++) { var identityProvider = realm.identityProviders[j]; var match = false; - - for (i = 0; i < $scope.application.allowedIdentityProviders.length; i++) { - var appProvider = $scope.application.allowedIdentityProviders[i]; - - if (appProvider == identityProvider.id) { - $scope.identityProviders[i] = identityProvider; - match = true; + var applicationProvider; + + for (i = 0; i < $scope.application.identityProviders.length; i++) { + applicationProvider = $scope.application.identityProviders[i]; + + if (applicationProvider) { + if (applicationProvider.retrieveToken) { + applicationProvider.retrieveToken = applicationProvider.retrieveToken.toString(); + } else { + applicationProvider.retrieveToken = false.toString(); + } + + if (applicationProvider.id == identityProvider.id) { + $scope.identityProviders[i] = {}; + $scope.identityProviders[i].identityProvider = identityProvider; + $scope.identityProviders[i].retrieveToken = applicationProvider.retrieveToken.toString(); + break; + } + + applicationProvider = null; } } - if (!match) { - var length = $scope.identityProviders.length; - - length = length + $scope.application.allowedIdentityProviders.length; + if (applicationProvider == null) { + var length = $scope.identityProviders.length + $scope.application.identityProviders.length; - $scope.identityProviders[length] = identityProvider; + $scope.identityProviders[length] = {}; + $scope.identityProviders[length].identityProvider = identityProvider; + $scope.identityProviders[length].retrieveToken = false.toString(); } } $scope.identityProviders = $scope.identityProviders.filter(function(n){ return n != undefined }); + var oldCopy = angular.copy($scope.application); + $scope.save = function() { var selectedProviders = []; - for (i = 0; i < $scope.application.allowedIdentityProviders.length; i++) { - var appProvider = $scope.application.allowedIdentityProviders[i]; + for (i = 0; i < $scope.application.identityProviders.length; i++) { + var appProvider = $scope.application.identityProviders[i]; - if (appProvider) { + if (appProvider.id != null && appProvider.id != false) { selectedProviders[selectedProviders.length] = appProvider; } } - $scope.allowedIdentityProviders = $scope.application.allowedIdentityProviders; - $scope.application.allowedIdentityProviders = selectedProviders; + $scope.application.identityProviders = selectedProviders; Application.update({ realm : realm.realm, application : application.id }, $scope.application, function() { $scope.changed = false; - $scope.application.allowedIdentityProviders = $scope.allowedIdentityProviders; - $location.url("/realms/" + realm.realm + "/applications/" + application.id + "/identity-provider"); + $route.reload(); Notifications.success("Your changes have been saved to the application."); }); }; $scope.reset = function() { - $scope.application = angular.copy(application); + $scope.application = angular.copy(oldCopy); $scope.changed = false; }; - $scope.$watch(function() { - return $location.path(); - }, function() { - $scope.path = $location.path().substring(1).split("/"); - }); + $scope.$watch('application', function() { + if (!angular.equals($scope.application, oldCopy)) { + $scope.changed = true; + } + }, true); }); module.controller('ApplicationSamlKeyCtrl', function($scope, $location, $http, $upload, realm, application, diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js index dec42f56f357..1237f573018c 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js @@ -324,74 +324,94 @@ module.controller('OAuthClientRevocationCtrl', function($scope, realm, oauth, OA } }); -module.controller('OAuthClientIdentityProviderCtrl', function($scope, realm, oauth, OAuthClient, $location, Notifications) { +module.controller('OAuthClientIdentityProviderCtrl', function($scope, $route, realm, oauth, OAuthClient, $location, Notifications) { $scope.realm = realm; $scope.oauth = angular.copy(oauth); + var length = 0; - $scope.identityProviders = []; + if ($scope.oauth.identityProviders) { + length = $scope.oauth.identityProviders.length; + } else { + $scope.oauth.identityProviders = new Array(realm.identityProviders.length); + } - if (!$scope.oauth.allowedIdentityProviders) { - $scope.oauth.allowedIdentityProviders = []; + for (j = length; j < realm.identityProviders.length; j++) { + $scope.oauth.identityProviders[j] = {}; } + $scope.identityProviders = []; + for (j = 0; j < realm.identityProviders.length; j++) { var identityProvider = realm.identityProviders[j]; var match = false; - - for (i = 0; i < $scope.oauth.allowedIdentityProviders.length; i++) { - var appProvider = $scope.oauth.allowedIdentityProviders[i]; - - if (appProvider == identityProvider.id) { - $scope.identityProviders[i] = identityProvider; - match = true; + var applicationProvider; + + for (i = 0; i < $scope.oauth.identityProviders.length; i++) { + applicationProvider = $scope.oauth.identityProviders[i]; + + if (applicationProvider) { + if (applicationProvider.retrieveToken) { + applicationProvider.retrieveToken = applicationProvider.retrieveToken.toString(); + } else { + applicationProvider.retrieveToken = false.toString(); + } + + if (applicationProvider.id == identityProvider.id) { + $scope.identityProviders[i] = {}; + $scope.identityProviders[i].identityProvider = identityProvider; + $scope.identityProviders[i].retrieveToken = applicationProvider.retrieveToken.toString(); + break; + } + + applicationProvider = null; } } - if (!match) { - var length = $scope.identityProviders.length; - - length = length + $scope.oauth.allowedIdentityProviders.length; + if (applicationProvider == null) { + var length = $scope.identityProviders.length + $scope.oauth.identityProviders.length; - $scope.identityProviders[length] = identityProvider; + $scope.identityProviders[length] = {}; + $scope.identityProviders[length].identityProvider = identityProvider; + $scope.identityProviders[length].retrieveToken = false.toString(); } } $scope.identityProviders = $scope.identityProviders.filter(function(n){ return n != undefined }); + var oldCopy = angular.copy($scope.oauth); + $scope.save = function() { var selectedProviders = []; - for (i = 0; i < $scope.oauth.allowedIdentityProviders.length; i++) { - var appProvider = $scope.oauth.allowedIdentityProviders[i]; + for (i = 0; i < $scope.oauth.identityProviders.length; i++) { + var appProvider = $scope.oauth.identityProviders[i]; - if (appProvider) { + if (appProvider.id != null && appProvider.id != false) { selectedProviders[selectedProviders.length] = appProvider; } } - $scope.allowedIdentityProviders = $scope.oauth.allowedIdentityProviders; - $scope.oauth.allowedIdentityProviders = selectedProviders; + $scope.oauth.identityProviders = selectedProviders; OAuthClient.update({ realm : realm.realm, oauth : oauth.id }, $scope.oauth, function() { $scope.changed = false; - $scope.oauth.allowedIdentityProviders = $scope.allowedIdentityProviders; - $location.url("/realms/" + realm.realm + "/oauth-clients/" + oauth.id + "/identity-provider"); + $route.reload(); Notifications.success("Your changes have been saved to the application."); }); }; $scope.reset = function() { - $scope.oauth = angular.copy(oauth); + $scope.oauth = angular.copy(oldCopy); $scope.changed = false; }; - $scope.$watch(function() { - return $location.path(); - }, function() { - $scope.path = $location.path().substring(1).split("/"); - }); + $scope.$watch('oauth', function() { + if (!angular.equals($scope.oauth, oldCopy)) { + $scope.changed = true; + } + }, true); }); diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html index 57adcd3f35c1..aeb3b8d961dc 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html @@ -7,17 +7,24 @@
  • {{application.name}}
  • Identity Provider
  • -

    {{application.name}} Identity Provider Settings

    +

    {{application.name}} Identity Provider Settings

    - {{identityProvider.name}} - + {{identityProvider.identityProvider.name}} +
    - + +
    +
    + +
    + +
    - + +
    diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html index 92f3e87124e2..619ce331ea2d 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html @@ -10,14 +10,21 @@

    {{oauth.name}} Identity Provider Settings

    - {{identityProvider.name}} - + {{identityProvider.identityProvider.name}} +
    - + +
    +
    + +
    + +
    - + +
    diff --git a/model/api/src/main/java/org/keycloak/models/ClientIdentityProviderMappingModel.java b/model/api/src/main/java/org/keycloak/models/ClientIdentityProviderMappingModel.java new file mode 100644 index 000000000000..e3b84014a802 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/ClientIdentityProviderMappingModel.java @@ -0,0 +1,43 @@ +/* + * JBoss, Home of Professional Open Source + * + * Copyright 2013 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models; + +/** + * @author pedroigor + */ +public class ClientIdentityProviderMappingModel { + + private String identityProvider; + private boolean retrieveToken; + + public String getIdentityProvider() { + return this.identityProvider; + } + + public void setIdentityProvider(String identityProviderModel) { + this.identityProvider = identityProviderModel; + } + + public boolean isRetrieveToken() { + return this.retrieveToken; + } + + public void setRetrieveToken(boolean retrieveToken) { + this.retrieveToken = retrieveToken; + } +} 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 5354d04225d7..3ebdc2aac798 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java @@ -98,11 +98,10 @@ public interface ClientModel { void setNotBefore(int notBefore); - void updateAllowedIdentityProviders(List identityProviders); - - List getAllowedIdentityProviders(); - + void updateAllowedIdentityProviders(List identityProviders); + List getIdentityProviders(); boolean hasIdentityProvider(String providerId); + boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId); Set getProtocolMappers(); void addProtocolMappers(Set mapperIds); 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 3d43feeb3d50..134d1bfaf107 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 @@ -29,7 +29,7 @@ public class ClientEntity extends AbstractIdentifiableEntity { private List webOrigins = new ArrayList(); private List redirectUris = new ArrayList(); private List scopeIds = new ArrayList(); - private List allowedIdentityProviders = new ArrayList(); + private List identityProviders = new ArrayList(); private Set protocolMappers = new HashSet(); public String getName() { @@ -144,12 +144,12 @@ public void setFrontchannelLogout(boolean frontchannelLogout) { this.frontchannelLogout = frontchannelLogout; } - public List getAllowedIdentityProviders() { - return this.allowedIdentityProviders; + public List getIdentityProviders() { + return this.identityProviders; } - public void setAllowedIdentityProviders(List allowedIdentityProviders) { - this.allowedIdentityProviders = allowedIdentityProviders; + public void setIdentityProviders(List identityProviders) { + this.identityProviders = identityProviders; } public Set getProtocolMappers() { diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientIdentityProviderMappingEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientIdentityProviderMappingEntity.java new file mode 100644 index 000000000000..a788aacf4bd2 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/entities/ClientIdentityProviderMappingEntity.java @@ -0,0 +1,44 @@ +/* + * JBoss, Home of Professional Open Source + * + * Copyright 2013 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.models.entities; + +/** + * @author pedroigor + */ +public class ClientIdentityProviderMappingEntity { + + private String id; + private Boolean retrieveToken; + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public Boolean isRetrieveToken() { + return this.retrieveToken; + } + + public void setRetrieveToken(Boolean retrieveToken) { + this.retrieveToken = retrieveToken; + } + +} 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 522c96572be3..7bd7cfaecc7a 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 @@ -3,6 +3,7 @@ import org.keycloak.models.ApplicationModel; import org.keycloak.models.ClaimMask; import org.keycloak.models.ClaimTypeModel; +import org.keycloak.models.ClientIdentityProviderMappingModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.FederatedIdentityModel; @@ -19,6 +20,7 @@ import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.ClaimRepresentation; import org.keycloak.representations.idm.ClaimTypeRepresentation; +import org.keycloak.representations.idm.ClientIdentityProviderMappingRepresentation; import org.keycloak.representations.idm.ClientProtocolMappingRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; @@ -262,8 +264,8 @@ public static ApplicationRepresentation toRepresentation(ApplicationModel applic rep.setRegisteredNodes(new HashMap(applicationModel.getRegisteredNodes())); } - if (!applicationModel.getAllowedIdentityProviders().isEmpty()) { - rep.setAllowedIdentityProviders(applicationModel.getAllowedIdentityProviders()); + if (!applicationModel.getIdentityProviders().isEmpty()) { + rep.setIdentityProviders(toRepresentation(applicationModel.getIdentityProviders())); } if (!applicationModel.getProtocolMappers().isEmpty()) { @@ -279,6 +281,21 @@ public static ApplicationRepresentation toRepresentation(ApplicationModel applic return rep; } + private static List toRepresentation(List identityProviders) { + ArrayList representations = new ArrayList(); + + for (ClientIdentityProviderMappingModel model : identityProviders) { + ClientIdentityProviderMappingRepresentation representation = new ClientIdentityProviderMappingRepresentation(); + + representation.setId(model.getIdentityProvider()); + representation.setRetrieveToken(model.isRetrieveToken()); + + representations.add(representation); + } + + return representations; + } + public static OAuthClientRepresentation toRepresentation(OAuthClientModel model) { OAuthClientRepresentation rep = new OAuthClientRepresentation(); rep.setId(model.getId()); @@ -301,8 +318,8 @@ public static OAuthClientRepresentation toRepresentation(OAuthClientModel model) } rep.setNotBefore(model.getNotBefore()); - if (!model.getAllowedIdentityProviders().isEmpty()) { - rep.setAllowedIdentityProviders(model.getAllowedIdentityProviders()); + if (!model.getIdentityProviders().isEmpty()) { + rep.setIdentityProviders(toRepresentation(model.getIdentityProviders())); } if (!model.getProtocolMappers().isEmpty()) { 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 f29676411451..2bf61e2395c1 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 @@ -7,6 +7,7 @@ import org.keycloak.models.BrowserSecurityHeaders; import org.keycloak.models.ClaimMask; import org.keycloak.models.ClaimTypeModel; +import org.keycloak.models.ClientIdentityProviderMappingModel; import org.keycloak.models.ClientModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderModel; @@ -23,6 +24,7 @@ import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.representations.idm.ClaimRepresentation; import org.keycloak.representations.idm.ClaimTypeRepresentation; +import org.keycloak.representations.idm.ClientIdentityProviderMappingRepresentation; import org.keycloak.representations.idm.ClientProtocolMappingRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; @@ -473,17 +475,7 @@ public static ApplicationModel createApplication(RealmModel realm, ApplicationRe applicationModel.setProtocolMappers(ids); } - List allowedIdentityProviders = resourceRep.getAllowedIdentityProviders(); - - if (allowedIdentityProviders == null || allowedIdentityProviders.isEmpty()) { - allowedIdentityProviders = new ArrayList(); - - for (IdentityProviderModel identityProvider : realm.getIdentityProviders()) { - allowedIdentityProviders.add(identityProvider.getId()); - } - } - - applicationModel.updateAllowedIdentityProviders(allowedIdentityProviders); + applicationModel.updateAllowedIdentityProviders(toModel(resourceRep.getIdentityProviders(), realm)); return applicationModel; } @@ -536,9 +528,7 @@ public static void updateApplication(ApplicationRepresentation rep, ApplicationM setClaims(resource, rep.getClaims()); } - if (rep.getAllowedIdentityProviders() != null) { - resource.updateAllowedIdentityProviders(rep.getAllowedIdentityProviders()); - } + updateClientIdentityProvides(rep.getIdentityProviders(), resource); } public static void setClaims(ClientModel model, ClaimRepresentation rep) { @@ -613,17 +603,7 @@ public static OAuthClientModel createOAuthClient(String id, String name, RealmMo public static OAuthClientModel createOAuthClient(OAuthClientRepresentation rep, RealmModel realm) { OAuthClientModel model = createOAuthClient(rep.getId(), rep.getName(), realm); - List allowedIdentityProviders = rep.getAllowedIdentityProviders(); - - if (allowedIdentityProviders == null || allowedIdentityProviders.isEmpty()) { - allowedIdentityProviders = new ArrayList(); - - for (IdentityProviderModel identityProvider : realm.getIdentityProviders()) { - allowedIdentityProviders.add(identityProvider.getId()); - } - } - - model.updateAllowedIdentityProviders(allowedIdentityProviders); + model.updateAllowedIdentityProviders(toModel(rep.getIdentityProviders(), realm)); updateOAuthClient(rep, model); return model; @@ -667,9 +647,7 @@ public static void updateOAuthClient(OAuthClientRepresentation rep, OAuthClientM } } - if (rep.getAllowedIdentityProviders() != null) { - model.updateAllowedIdentityProviders(rep.getAllowedIdentityProviders()); - } + updateClientIdentityProvides(rep.getIdentityProviders(), model); if (rep.getProtocolMappers() != null) { Set ids = new HashSet(); @@ -868,4 +846,48 @@ public static ProtocolMapperModel toModel(ProtocolMapperRepresentation rep) { model.setConfig(rep.getConfig()); return model; } + + private static List toModel(List repIdentityProviders, RealmModel realm) { + List allowedIdentityProviders = new ArrayList(); + + if (repIdentityProviders == null || repIdentityProviders.isEmpty()) { + allowedIdentityProviders = new ArrayList(); + + for (IdentityProviderModel identityProvider : realm.getIdentityProviders()) { + ClientIdentityProviderMappingModel identityProviderMapping = new ClientIdentityProviderMappingModel(); + + identityProviderMapping.setIdentityProvider(identityProvider.getId()); + + allowedIdentityProviders.add(identityProviderMapping); + } + } else { + for (ClientIdentityProviderMappingRepresentation rep : repIdentityProviders) { + ClientIdentityProviderMappingModel identityProviderMapping = new ClientIdentityProviderMappingModel(); + + identityProviderMapping.setIdentityProvider(rep.getId()); + identityProviderMapping.setRetrieveToken(rep.isRetrieveToken()); + + allowedIdentityProviders.add(identityProviderMapping); + } + } + + return allowedIdentityProviders; + } + + private static void updateClientIdentityProvides(List identityProviders, ClientModel resource) { + if (identityProviders != null) { + List allowedIdentityProviders = new ArrayList(); + + for (ClientIdentityProviderMappingRepresentation mappingRepresentation : identityProviders) { + ClientIdentityProviderMappingModel identityProviderMapping = new ClientIdentityProviderMappingModel(); + + identityProviderMapping.setIdentityProvider(mappingRepresentation.getId()); + identityProviderMapping.setRetrieveToken(mappingRepresentation.isRetrieveToken()); + + allowedIdentityProviders.add(identityProviderMapping); + } + + resource.updateAllowedIdentityProviders(allowedIdentityProviders); + } + } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java index 0fc38bcc9dd0..a17cc7c14a87 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java @@ -1,5 +1,6 @@ package org.keycloak.models.cache; +import org.keycloak.models.ClientIdentityProviderMappingModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; @@ -263,15 +264,15 @@ public Map getAttributes() { } @Override - public void updateAllowedIdentityProviders(List identityProviders) { + public void updateAllowedIdentityProviders(List identityProviders) { getDelegateForUpdate(); updatedClient.updateAllowedIdentityProviders(identityProviders); } @Override - public List getAllowedIdentityProviders() { - if (updatedClient != null) return updatedClient.getAllowedIdentityProviders(); - return cachedClient.getAllowedIdentityProviders(); + public List getIdentityProviders() { + if (updatedClient != null) return updatedClient.getIdentityProviders(); + return cachedClient.getIdentityProviders(); } @Override @@ -280,6 +281,12 @@ public boolean hasIdentityProvider(String providerId) { return cachedClient.hasIdentityProvider(providerId); } + @Override + public boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId) { + if (updatedClient != null) return updatedClient.isAllowedRetrieveTokenFromIdentityProvider(providerId); + return cachedClient.isAllowedRetrieveTokenFromIdentityProvider(providerId); + } + @Override public Set getProtocolMappers() { if (updatedClient != null) return updatedClient.getProtocolMappers(); 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 1029713ba643..e9a8a89b748c 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 @@ -1,5 +1,6 @@ package org.keycloak.models.cache.entities; +import org.keycloak.models.ClientIdentityProviderMappingModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; @@ -35,7 +36,7 @@ public class CachedClient { protected int notBefore; protected Set scope = new HashSet(); protected Set webOrigins = new HashSet(); - private List allowedIdentityProviders = new ArrayList(); + private List identityProviders = new ArrayList(); private Set protocolClaimMappings = new HashSet(); public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) { @@ -57,7 +58,7 @@ public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, for (RoleModel role : model.getScopeMappings()) { scope.add(role.getId()); } - this.allowedIdentityProviders = model.getAllowedIdentityProviders(); + this.identityProviders = model.getIdentityProviders(); protocolClaimMappings.addAll(model.getProtocolMappers()); } @@ -125,15 +126,31 @@ public boolean isFrontchannelLogout() { return frontchannelLogout; } - public List getAllowedIdentityProviders() { - return this.allowedIdentityProviders; + public List getIdentityProviders() { + return this.identityProviders; } public boolean hasIdentityProvider(String providerId) { - return this.allowedIdentityProviders.contains(providerId); + for (ClientIdentityProviderMappingModel model : getIdentityProviders()) { + if (model.getIdentityProvider().equals(providerId)) { + return true; + } + } + + return false; } public Set getProtocolClaimMappings() { return protocolClaimMappings; } + + public boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId) { + for (ClientIdentityProviderMappingModel model : getIdentityProviders()) { + if (model.getIdentityProvider().equals(providerId)) { + return model.isRetrieveToken(); + } + } + + return false; + } } 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 c437014cbc0e..04af5ee4a402 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 @@ -1,14 +1,15 @@ package org.keycloak.models.jpa; +import org.keycloak.models.ClientIdentityProviderMappingModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; import org.keycloak.models.jpa.entities.ClientEntity; +import org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity; import org.keycloak.models.jpa.entities.IdentityProviderEntity; import org.keycloak.models.jpa.entities.ProtocolMapperEntity; -import org.keycloak.models.jpa.entities.RealmEntity; import org.keycloak.models.jpa.entities.RoleEntity; import org.keycloak.models.jpa.entities.ScopeMappingEntity; @@ -303,47 +304,96 @@ public Map getAttributes() { } @Override - public void updateAllowedIdentityProviders(List identityProviders) { - Collection entities = entity.getAllowedIdentityProviders(); + public void updateAllowedIdentityProviders(List identityProviders) { + Collection entities = entity.getIdentityProviders(); Set already = new HashSet(); - List remove = new ArrayList(); - for (IdentityProviderEntity rel : entities) { - if (!contains(rel.getId(), identityProviders.toArray(new String[identityProviders.size()]))) { - remove.add(rel); + List remove = new ArrayList(); + + for (ClientIdentityProviderMappingEntity entity : entities) { + IdentityProviderEntity identityProvider = entity.getIdentityProvider(); + boolean toRemove = true; + + for (ClientIdentityProviderMappingModel model : identityProviders) { + if (model.getIdentityProvider().equals(identityProvider.getId())) { + toRemove = false; + break; + } + } + + if (toRemove) { + remove.add(entity); } else { - already.add(rel.getId()); + already.add(entity.getIdentityProvider().getId()); } } - for (IdentityProviderEntity entity : remove) { + for (ClientIdentityProviderMappingEntity entity : remove) { entities.remove(entity); + em.remove(entity); } em.flush(); - for (String providerId : identityProviders) { - if (!already.contains(providerId)) { - TypedQuery query = em.createNamedQuery("findIdentityProviderById", IdentityProviderEntity.class).setParameter("id", providerId); - IdentityProviderEntity providerEntity = query.getSingleResult(); - entities.add(providerEntity); + for (ClientIdentityProviderMappingModel model : identityProviders) { + ClientIdentityProviderMappingEntity mappingEntity = null; + + if (!already.contains(model.getIdentityProvider())) { + mappingEntity = new ClientIdentityProviderMappingEntity(); + entities.add(mappingEntity); + } else { + for (ClientIdentityProviderMappingEntity entity : entities) { + if (entity.getIdentityProvider().getId().equals(model.getIdentityProvider())) { + mappingEntity = entity; + break; + } + } } + + TypedQuery query = em.createNamedQuery("findIdentityProviderById", IdentityProviderEntity.class).setParameter("id", model.getIdentityProvider()); + IdentityProviderEntity identityProviderEntity = query.getSingleResult(); + + mappingEntity.setIdentityProvider(identityProviderEntity); + mappingEntity.setClient(this.entity); + mappingEntity.setRetrieveToken(model.isRetrieveToken()); + + em.persist(mappingEntity); } em.flush(); } @Override - public List getAllowedIdentityProviders() { - Collection entities = entity.getAllowedIdentityProviders(); - List providers = new ArrayList(); + public List getIdentityProviders() { + List models = new ArrayList(); + + for (ClientIdentityProviderMappingEntity entity : this.entity.getIdentityProviders()) { + ClientIdentityProviderMappingModel model = new ClientIdentityProviderMappingModel(); - for (IdentityProviderEntity entity : entities) { - providers.add(entity.getId()); + model.setIdentityProvider(entity.getIdentityProvider().getId()); + model.setRetrieveToken(entity.isRetrieveToken()); + + models.add(model); } - return providers; + return models; } @Override public boolean hasIdentityProvider(String providerId) { - List allowedIdentityProviders = getAllowedIdentityProviders(); - return allowedIdentityProviders.contains(providerId); + for (ClientIdentityProviderMappingModel model : getIdentityProviders()) { + if (model.getIdentityProvider().equals(providerId)) { + return true; + } + } + + return false; + } + + @Override + public boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId) { + for (ClientIdentityProviderMappingModel model : getIdentityProviders()) { + if (model.getIdentityProvider().equals(providerId)) { + return model.isRetrieveToken(); + } + } + + return false; } public static boolean contains(String str, String[] array) { 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 817fd8085c5b..dcf33cb7695c 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 @@ -73,9 +73,8 @@ public abstract class ClientEntity { @CollectionTable(name="CLIENT_ATTRIBUTES", joinColumns={ @JoinColumn(name="CLIENT_ID") }) protected Map attributes = new HashMap(); - @OneToMany(fetch = FetchType.LAZY) - @JoinTable(name="CLIENT_ALLOWED_IDENTITY_PROVIDER", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="INTERNAL_ID")}) - Collection allowedIdentityProviders = new ArrayList(); + @OneToMany(fetch = FetchType.LAZY, mappedBy = "client", cascade = CascadeType.REMOVE) + Collection identityProviders = new ArrayList(); @OneToMany(fetch = FetchType.LAZY) @JoinTable(name="CLIENT_PROTOCOL_MAPPER", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="MAPPING_ID")}) @@ -193,12 +192,12 @@ public void setFrontchannelLogout(boolean frontchannelLogout) { this.frontchannelLogout = frontchannelLogout; } - public Collection getAllowedIdentityProviders() { - return this.allowedIdentityProviders; + public Collection getIdentityProviders() { + return this.identityProviders; } - public void setAllowedIdentityProviders(Collection allowedIdentityProviders) { - this.allowedIdentityProviders = allowedIdentityProviders; + public void setIdentityProviders(Collection identityProviders) { + this.identityProviders = identityProviders; } public Collection getProtocolMappers() { diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientIdentityProviderMappingEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientIdentityProviderMappingEntity.java new file mode 100755 index 000000000000..fe8485d6086b --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientIdentityProviderMappingEntity.java @@ -0,0 +1,121 @@ +package org.keycloak.models.jpa.entities; + +import javax.persistence.Column; +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.Table; +import java.io.Serializable; + +/** + * @author pedroigor + */ +@Table(name="CLIENT_IDENTITY_PROVIDER_MAPPING") +@Entity +@IdClass(ClientIdentityProviderMappingEntity.Key.class) +public class ClientIdentityProviderMappingEntity { + + @Id + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CLIENT_ID") + private ClientEntity client; + + @Id + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "IDENTITY_PROVIDER_ID") + private IdentityProviderEntity identityProvider; + + @Column(name = "RETRIEVE_TOKEN") + private boolean retrieveToken; + + public ClientEntity getClient() { + return this.client; + } + + public void setClient(ClientEntity client) { + this.client = client; + } + + public IdentityProviderEntity getIdentityProvider() { + return this.identityProvider; + } + + public void setIdentityProvider(IdentityProviderEntity identityProvider) { + this.identityProvider = identityProvider; + } + + public void setRetrieveToken(boolean retrieveToken) { + this.retrieveToken = retrieveToken; + } + + public boolean isRetrieveToken() { + return retrieveToken; + } + + public static class Key implements Serializable { + + private ClientEntity client; + private IdentityProviderEntity identityProvider; + + public Key() { + } + + public Key(ClientEntity client, IdentityProviderEntity identityProvider) { + this.client = client; + this.identityProvider = identityProvider; + } + + public ClientEntity getUser() { + return client; + } + + public IdentityProviderEntity getIdentityProvider() { + return identityProvider; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (identityProvider != null ? !identityProvider.getId().equals(key.identityProvider.getId()) : key.identityProvider != null) + return false; + if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = client != null ? client.getId().hashCode() : 0; + result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0); + return result; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ClientIdentityProviderMappingEntity key = (ClientIdentityProviderMappingEntity) o; + + if (identityProvider != null ? !identityProvider.getId().equals(key.identityProvider.getId()) : key.identityProvider != null) + return false; + if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = client != null ? client.getId().hashCode() : 0; + result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0); + return result; + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java index 3c472821f697..c0202c1f5230 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 @@ -2,6 +2,7 @@ import org.keycloak.connections.mongo.api.MongoIdentifiableEntity; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; +import org.keycloak.models.ClientIdentityProviderMappingModel; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; @@ -9,6 +10,7 @@ import org.keycloak.models.RealmProvider; import org.keycloak.models.RoleModel; import org.keycloak.models.entities.ClientEntity; +import org.keycloak.models.entities.ClientIdentityProviderMappingEntity; import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; import org.keycloak.models.mongo.utils.MongoModelUtils; @@ -323,24 +325,58 @@ public void setProtocolMappers(Set mapperIds) { } @Override - public void updateAllowedIdentityProviders(List identityProviders) { - List providerIds = new ArrayList(); - for (String providerId : identityProviders) { - providerIds.add(providerId); + public void updateAllowedIdentityProviders(List identityProviders) { + List stored = getMongoEntityAsClient().getIdentityProviders(); + + for (ClientIdentityProviderMappingModel model : identityProviders) { + ClientIdentityProviderMappingEntity entity = new ClientIdentityProviderMappingEntity(); + + entity.setId(model.getIdentityProvider()); + entity.setRetrieveToken(model.isRetrieveToken()); } - getMongoEntityAsClient().setAllowedIdentityProviders(identityProviders); + getMongoEntityAsClient().setIdentityProviders(stored); updateMongoEntity(); } @Override - public List getAllowedIdentityProviders() { - return getMongoEntityAsClient().getAllowedIdentityProviders(); + public List getIdentityProviders() { + List models = new ArrayList(); + + for (ClientIdentityProviderMappingEntity entity : getMongoEntityAsClient().getIdentityProviders()) { + ClientIdentityProviderMappingModel model = new ClientIdentityProviderMappingModel(); + + model.setIdentityProvider(entity.getId()); + model.setRetrieveToken(entity.isRetrieveToken()); + + models.add(model); + } + + return models; } @Override public boolean hasIdentityProvider(String providerId) { - List allowedIdentityProviders = getMongoEntityAsClient().getAllowedIdentityProviders(); - return allowedIdentityProviders.contains(providerId); + for (ClientIdentityProviderMappingEntity identityProviderMappingModel : getMongoEntityAsClient().getIdentityProviders()) { + String identityProvider = identityProviderMappingModel.getId(); + + if (identityProvider.equals(providerId)) { + return true; + } + } + + return false; + } + + @Override + public boolean isAllowedRetrieveTokenFromIdentityProvider(String providerId) { + for (ClientIdentityProviderMappingEntity identityProviderMappingModel : getMongoEntityAsClient().getIdentityProviders()) { + if (identityProviderMappingModel.getId().equals(providerId)) { + return identityProviderMappingModel.isRetrieveToken(); + } + } + + return false; } + } diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 5f9501ecc32b..7fb4209ca464 100644 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -186,6 +186,10 @@ private Response getToken(String providerId, boolean forceRetrieval) { return corsResponse(badRequest("Client [" + audience + "] not authorized."), clientModel); } + if (!clientModel.isAllowedRetrieveTokenFromIdentityProvider(providerId)) { + return corsResponse(badRequest("Client [" + audience + "] not authorized to retrieve tokens from identity provider [" + providerId + "]."), clientModel); + } + if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) { return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo) .setClientSessionCode(authManager.extractAuthorizationHeaderToken(this.request.getHttpHeaders())) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java index e8b184edce48..bac9b03027e0 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java @@ -1,6 +1,7 @@ package org.keycloak.services.resources.admin; import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.models.ClientIdentityProviderMappingModel; import org.keycloak.models.ClientModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; @@ -17,6 +18,7 @@ import javax.ws.rs.PUT; import javax.ws.rs.Produces; import javax.ws.rs.core.Response; +import java.util.ArrayList; import java.util.List; /** @@ -63,11 +65,15 @@ public Response update(IdentityProviderRepresentation model) { private void removeClientIdentityProviders(List clients, IdentityProviderModel identityProvider) { for (ClientModel clientModel : clients) { - List allowedIdentityProviders = clientModel.getAllowedIdentityProviders(); + List identityProviders = clientModel.getIdentityProviders(); - allowedIdentityProviders.remove(identityProvider.getId()); + for (ClientIdentityProviderMappingModel providerMappingModel : new ArrayList(identityProviders)) { + if (providerMappingModel.getIdentityProvider().equals(identityProvider.getId())) { + identityProviders.remove(providerMappingModel); + } + } - clientModel.updateAllowedIdentityProviders(allowedIdentityProviders); + clientModel.updateAllowedIdentityProviders(identityProviders); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java index d44e464c4447..d7d1a679b3f8 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java @@ -7,6 +7,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.broker.provider.IdentityProvider; import org.keycloak.broker.provider.IdentityProviderFactory; +import org.keycloak.models.ClientIdentityProviderMappingModel; import org.keycloak.models.ClientModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; @@ -179,9 +180,12 @@ private List getProviderFactories() { private void updateClientIdentityProviders(List clients, IdentityProviderRepresentation identityProvider) { for (ClientModel clientModel : clients) { - List allowedIdentityProviders = clientModel.getAllowedIdentityProviders(); + List allowedIdentityProviders = clientModel.getIdentityProviders(); + ClientIdentityProviderMappingModel providerMappingModel = new ClientIdentityProviderMappingModel(); - allowedIdentityProviders.add(identityProvider.getId()); + providerMappingModel.setIdentityProvider(identityProvider.getId()); + + allowedIdentityProviders.add(providerMappingModel); clientModel.updateAllowedIdentityProviders(allowedIdentityProviders); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java index 6921ec3132e0..dd034c1e822d 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java @@ -25,6 +25,8 @@ import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.models.ApplicationModel; +import org.keycloak.models.ClientIdentityProviderMappingModel; +import org.keycloak.models.ClientModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; @@ -156,11 +158,18 @@ public void testDisabledForApplication() { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); RealmModel realm = getRealm(); ApplicationModel applicationModel = realm.getApplicationByName("test-app"); - List allowedIdentityProviders = applicationModel.getAllowedIdentityProviders(); + List allowedIdentityProviders = applicationModel.getIdentityProviders(); + ClientIdentityProviderMappingModel mapping = null; - assertTrue(allowedIdentityProviders.contains(identityProviderModel.getId())); + for (ClientIdentityProviderMappingModel model : allowedIdentityProviders) { + if (model.getIdentityProvider().equals(identityProviderModel.getId())) { + mapping = model; + } + } - allowedIdentityProviders.remove(identityProviderModel.getId()); + assertNotNull(mapping); + + allowedIdentityProviders.remove(mapping); this.driver.navigate().to("http://localhost:8081/test-app/"); @@ -173,7 +182,7 @@ public void testDisabledForApplication() { } - allowedIdentityProviders.add(identityProviderModel.getId()); + allowedIdentityProviders.add(mapping); applicationModel.updateAllowedIdentityProviders(allowedIdentityProviders); @@ -317,6 +326,18 @@ public void testTokenStorageAndRetrievalByApplication() { assertNotNull(identityModel.getToken()); + ClientModel clientModel = realm.findClient("test-app"); + ClientIdentityProviderMappingModel providerMappingModel = null; + + for (ClientIdentityProviderMappingModel identityProviderMappingModel : clientModel.getIdentityProviders()) { + if (identityProviderMappingModel.getIdentityProvider().equals(getProviderId())) { + providerMappingModel = identityProviderMappingModel; + break; + } + } + + providerMappingModel.setRetrieveToken(false); + UserSessionStatus userSessionStatus = retrieveSessionStatus(); String accessToken = userSessionStatus.getAccessTokenString(); URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName()); @@ -331,6 +352,14 @@ public void filter(ClientRequestContext requestContext) throws IOException { WebTarget tokenEndpoint = client.target(tokenEndpointUrl); Response response = tokenEndpoint.request().get(); + assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + + providerMappingModel.setRetrieveToken(true); + + client = ClientBuilder.newBuilder().register(authFilter).build(); + tokenEndpoint = client.target(tokenEndpointUrl); + response = tokenEndpoint.request().get(); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); assertNotNull(response.readEntity(String.class)); @@ -375,6 +404,18 @@ public void testTokenStorageAndRetrievalByOAuthClient() { assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.CODE)); + ClientModel clientModel = getRealm().findClient("third-party"); + ClientIdentityProviderMappingModel providerMappingModel = null; + + for (ClientIdentityProviderMappingModel identityProviderMappingModel : clientModel.getIdentityProviders()) { + if (identityProviderMappingModel.getIdentityProvider().equals(getProviderId())) { + providerMappingModel = identityProviderMappingModel; + break; + } + } + + providerMappingModel.setRetrieveToken(true); + AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password"); URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), getRealm().getName()); String authHeader = "Bearer " + accessToken.getAccessToken(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java index d654d1e5d97f..8f43860e05c2 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java @@ -25,6 +25,8 @@ import org.keycloak.broker.saml.SAMLIdentityProvider; import org.keycloak.broker.saml.SAMLIdentityProviderConfig; import org.keycloak.broker.saml.SAMLIdentityProviderFactory; +import org.keycloak.models.ClientIdentityProviderMappingModel; +import org.keycloak.models.ClientModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.RealmModel; import org.keycloak.representations.idm.RealmRepresentation; @@ -113,6 +115,31 @@ public void testUpdateIdentityProvider() throws Exception { assertFalse(identityProviderModel.isAuthenticateByDefault()); } + @Test + public void testApplicationIdentityProviders() throws Exception { + RealmModel realm = installTestRealm(); + + ClientModel client = realm.findClient("test-app-with-allowed-providers"); + List identityProviders = client.getIdentityProviders(); + + assertEquals(1, identityProviders.size()); + + ClientIdentityProviderMappingModel identityProviderMappingModel = identityProviders.get(0); + + assertEquals("kc-oidc-idp", identityProviderMappingModel.getIdentityProvider()); + assertEquals(false, identityProviderMappingModel.isRetrieveToken()); + + identityProviders.remove(identityProviderMappingModel); + + client.updateAllowedIdentityProviders(identityProviders); + + client = realm.findClientById(client.getId()); + identityProviders = client.getIdentityProviders(); + + assertEquals(0, identityProviders.size()); + } + + private void assertIdentityProviderConfig(List identityProviders) { assertFalse(identityProviders.isEmpty()); diff --git a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json index 322cebe42337..c567c177df49 100755 --- a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json +++ b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json @@ -197,8 +197,11 @@ "/test-app/*" ], "webOrigins": [], - "allowedIdentityProviders": [ - "kc-oidc-idp" + "identityProviders": [ + { + "id": "kc-oidc-idp", + "retrieveToken": false + } ] } ],