From ff1f10d7a712f44a2320647d3fc4b4c3620d0777 Mon Sep 17 00:00:00 2001 From: pedroigor Date: Mon, 9 Feb 2015 21:30:21 -0200 Subject: [PATCH] [KEYCLOAK-883] - Refactoring to services endpoints and exposing them through admin client. --- .../idm/IdentityProviderRepresentation.java | 9 + .../theme/admin/base/resources/js/app.js | 20 ++- .../base/resources/js/controllers/realm.js | 5 +- .../resource/IdentityProviderResource.java | 27 +++ .../resource/IdentityProvidersResource.java | 29 +++ .../admin/client/resource/RealmResource.java | 3 + .../java/org/keycloak/models/AdminRoles.java | 4 +- .../models/utils/ModelToRepresentation.java | 26 +-- .../models/utils/RepresentationToModel.java | 29 +-- .../AuthenticationBrokerResource.java | 24 ++- .../admin/IdentityProviderResource.java | 147 ++------------- .../admin/IdentityProvidersResource.java | 169 ++++++++++++++++++ .../resources/admin/RealmAdminResource.java | 4 +- .../services/resources/admin/RealmAuth.java | 6 +- .../testsuite/admin/AbstractClientTest.java | 3 + .../testsuite/admin/IdentityProviderTest.java | 123 +++++++++++++ 16 files changed, 462 insertions(+), 166 deletions(-) create mode 100755 integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProviderResource.java create mode 100755 integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProvidersResource.java create mode 100755 services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java index cc4b3e60b3c9..b9f09c34ebc5 100644 --- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java @@ -26,6 +26,7 @@ public class IdentityProviderRepresentation { protected String id; + protected String internalId; protected String providerId; protected String name; protected boolean enabled = true; @@ -34,6 +35,14 @@ public class IdentityProviderRepresentation { protected String groupName; protected Map config = new HashMap(); + public String getInternalId() { + return this.internalId; + } + + public void setInternalId(String internalId) { + this.internalId = internalId; + } + public String getId() { return this.id; } diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js index 13db0219c2d2..7a31d03a7aea 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js @@ -160,7 +160,25 @@ module.config([ '$routeProvider', function($routeProvider) { return {}; }, providerFactory : function(IdentityProviderFactoryLoader) { - return IdentityProviderFactoryLoader(); + return {}; + } + }, + controller : 'RealmIdentityProviderCtrl' + }) + .when('/create/identity-provider/:realm/:provider_id', { + templateUrl : function(params){ return 'partials/realm-identity-provider-' + params.provider_id + '.html'; }, + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + serverInfo : function(ServerInfoLoader) { + return ServerInfoLoader(); + }, + instance : function(IdentityProviderLoader) { + return {}; + }, + providerFactory : function(IdentityProviderFactoryLoader) { + return new IdentityProviderFactoryLoader(); } }, controller : 'RealmIdentityProviderCtrl' diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js index ec05bb05e531..98a6e6b3538d 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js @@ -722,7 +722,7 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload $scope.callbackUrl = $location.absUrl().replace(/\/admin.*/, "/broker/") + realm.realm + "/" ; $scope.addProvider = function(provider) { - $location.url("/realms/" + realm.realm + "/identity-provider-settings/provider/" + provider.id + "/" + provider.id); + $location.url("/create/identity-provider/" + realm.realm + "/" + provider.id); }; $scope.remove = function() { @@ -746,7 +746,8 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload }); } else { IdentityProvider.update({ - realm: $scope.realm.realm + realm: $scope.realm.realm, + id: $scope.identityProvider.internalId }, $scope.identityProvider, function () { $location.url("/realms/" + realm.realm + "/identity-provider-settings"); Notifications.success("The " + $scope.identityProvider.name + " provider has been update."); diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProviderResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProviderResource.java new file mode 100755 index 000000000000..48a9d5b8a522 --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProviderResource.java @@ -0,0 +1,27 @@ +package org.keycloak.admin.client.resource; + +import org.keycloak.representations.idm.IdentityProviderRepresentation; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +/** + * @author pedroigor + */ +public interface IdentityProviderResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + IdentityProviderRepresentation toRepresentation(); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + void update(IdentityProviderRepresentation identityProviderRepresentation); + + @DELETE + void remove(); +} \ No newline at end of file diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProvidersResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProvidersResource.java new file mode 100755 index 000000000000..16ae0e969b11 --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/IdentityProvidersResource.java @@ -0,0 +1,29 @@ +package org.keycloak.admin.client.resource; + +import org.keycloak.representations.idm.IdentityProviderRepresentation; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import java.util.List; + +/** + * @author pedroigor + */ +public interface IdentityProvidersResource { + + @Path("{id}") + IdentityProviderResource get(@PathParam("id") String id); + + @GET + @Produces(MediaType.APPLICATION_JSON) + List findAll(); + + @POST + @Consumes(MediaType.APPLICATION_JSON) + void create(IdentityProviderRepresentation identityProvider); +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java index fa7309ab90c0..1ed0a8055ba3 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java @@ -35,6 +35,9 @@ public interface RealmResource { @Path("roles") public RolesResource roles(); + @Path("identity-provider") + IdentityProvidersResource identityProviders(); + @DELETE public void remove(); diff --git a/model/api/src/main/java/org/keycloak/models/AdminRoles.java b/model/api/src/main/java/org/keycloak/models/AdminRoles.java index a9889516a945..8d465058eb5d 100755 --- a/model/api/src/main/java/org/keycloak/models/AdminRoles.java +++ b/model/api/src/main/java/org/keycloak/models/AdminRoles.java @@ -19,13 +19,15 @@ public class AdminRoles { public static String VIEW_APPLICATIONS = "view-applications"; public static String VIEW_CLIENTS = "view-clients"; public static String VIEW_EVENTS = "view-events"; + public static String VIEW_IDENTITY_PROVIDERS = "view-identity-providers"; public static String MANAGE_REALM = "manage-realm"; public static String MANAGE_USERS = "manage-users"; public static String MANAGE_APPLICATIONS = "manage-applications"; + public static String MANAGE_IDENTITY_PROVIDERS = "manage-identity-providers"; public static String MANAGE_CLIENTS = "manage-clients"; public static String MANAGE_EVENTS = "manage-events"; - public static String[] ALL_REALM_ROLES = {VIEW_REALM, VIEW_USERS, VIEW_APPLICATIONS, VIEW_CLIENTS, VIEW_EVENTS, MANAGE_REALM, MANAGE_USERS, MANAGE_APPLICATIONS, MANAGE_CLIENTS, MANAGE_EVENTS}; + public static String[] ALL_REALM_ROLES = {VIEW_REALM, VIEW_USERS, VIEW_APPLICATIONS, VIEW_CLIENTS, VIEW_EVENTS, VIEW_IDENTITY_PROVIDERS, MANAGE_REALM, MANAGE_USERS, MANAGE_APPLICATIONS, MANAGE_CLIENTS, MANAGE_EVENTS, MANAGE_IDENTITY_PROVIDERS}; } 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 8fe96880a09d..f89acaa1da18 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 @@ -146,16 +146,7 @@ public static RealmRepresentation toRepresentation(RealmModel realm, boolean int } for (IdentityProviderModel provider : realm.getIdentityProviders()) { - IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation(); - - providerRep.setProviderId(provider.getProviderId()); - providerRep.setId(provider.getId()); - providerRep.setName(provider.getName()); - providerRep.setEnabled(provider.isEnabled()); - providerRep.setUpdateProfileFirstLogin(provider.isUpdateProfileFirstLogin()); - providerRep.setConfig(provider.getConfig()); - - rep.addIdentityProvider(providerRep); + rep.addIdentityProvider(toRepresentation(provider)); } return rep; @@ -306,4 +297,19 @@ public static UserFederationProviderRepresentation toRepresentation(UserFederati rep.setLastSync(model.getLastSync()); return rep; } + + public static IdentityProviderRepresentation toRepresentation(IdentityProviderModel identityProviderModel) { + IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation(); + + providerRep.setInternalId(identityProviderModel.getInternalId()); + providerRep.setProviderId(identityProviderModel.getProviderId()); + providerRep.setId(identityProviderModel.getId()); + providerRep.setName(identityProviderModel.getName()); + providerRep.setEnabled(identityProviderModel.isEnabled()); + providerRep.setStoreToken(identityProviderModel.isStoreToken()); + providerRep.setUpdateProfileFirstLogin(identityProviderModel.isUpdateProfileFirstLogin()); + providerRep.setConfig(identityProviderModel.getConfig()); + + return providerRep; + } } 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 0c7835ca56dd..846316823764 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 @@ -744,19 +744,24 @@ public static void createApplicationRoleMappings(ApplicationModel applicationMod private static void importIdentityProviders(RealmRepresentation rep, RealmModel newRealm) { if (rep.getIdentityProviders() != null) { - for (IdentityProviderRepresentation identityProviderRepresentation : rep.getIdentityProviders()) { - IdentityProviderModel identityProviderModel = new IdentityProviderModel(); - - identityProviderModel.setId(identityProviderRepresentation.getId()); - identityProviderModel.setProviderId(identityProviderRepresentation.getProviderId()); - identityProviderModel.setName(identityProviderRepresentation.getName()); - identityProviderModel.setEnabled(identityProviderRepresentation.isEnabled()); - identityProviderModel.setUpdateProfileFirstLogin(identityProviderRepresentation.isUpdateProfileFirstLogin()); - identityProviderModel.setStoreToken(identityProviderRepresentation.isStoreToken()); - identityProviderModel.setConfig(identityProviderRepresentation.getConfig()); - - newRealm.addIdentityProvider(identityProviderModel); + for (IdentityProviderRepresentation representation : rep.getIdentityProviders()) { + newRealm.addIdentityProvider(toModel(representation)); } } } + + public static IdentityProviderModel toModel(IdentityProviderRepresentation representation) { + IdentityProviderModel identityProviderModel = new IdentityProviderModel(); + + identityProviderModel.setInternalId(representation.getInternalId()); + identityProviderModel.setId(representation.getId()); + identityProviderModel.setProviderId(representation.getProviderId()); + identityProviderModel.setName(representation.getName()); + identityProviderModel.setEnabled(representation.isEnabled()); + identityProviderModel.setUpdateProfileFirstLogin(representation.isUpdateProfileFirstLogin()); + identityProviderModel.setStoreToken(representation.isStoreToken()); + identityProviderModel.setConfig(representation.getConfig()); + + return identityProviderModel; + } } diff --git a/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java b/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java index 91b0fb018e8e..5320eeeb6e17 100644 --- a/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java +++ b/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java @@ -204,6 +204,10 @@ private Response getToken(String realmName, String providerId, boolean forceRetr if (identityProviderConfig.isStoreToken()) { FederatedIdentityModel identity = this.session.users().getFederatedIdentity(authResult.getUser(), providerId, realm); + if (identity == null) { + return corsResponse(Flows.errors().error("User [" + authResult.getUser().getId() + "] is not associated with identity provider [" + providerId + "].", Response.Status.FORBIDDEN), clientModel); + } + return corsResponse(identityProvider.retrieveToken(identity), clientModel); } @@ -229,17 +233,19 @@ private Response handleResponse(String realmName, String providerId) { RealmManager realmManager = new RealmManager(session); RealmModel realm = realmManager.getRealmByName(realmName); + IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(realm, providerId); + try { - IdentityProvider provider = getIdentityProvider(realm, providerId); + IdentityProvider identityProvider = getIdentityProvider(realm, providerId); - if (provider == null) { - return Flows.forms(session, realm, null, uriInfo).setError("Social provider not found").createErrorPage(); + if (identityProvider == null) { + return Flows.forms(session, realm, null, uriInfo).setError("Social identityProvider not found").createErrorPage(); } - String relayState = provider.getRelayState(createAuthenticationRequest(providerId, null, realm, null)); + String relayState = identityProvider.getRelayState(createAuthenticationRequest(providerId, null, realm, null)); if (relayState == null) { - return redirectToErrorPage(realm, "No relay state from identity provider."); + return redirectToErrorPage(realm, "No relay state from identity identityProvider."); } ClientSessionCode clientCode = isValidAuthorizationCode(relayState, realm); @@ -256,7 +262,7 @@ private Response handleResponse(String realmName, String providerId) { return response; } - AuthenticationResponse authenticationResponse = provider.handleResponse(createAuthenticationRequest(providerId, null, realm, clientSession)); + AuthenticationResponse authenticationResponse = identityProvider.handleResponse(createAuthenticationRequest(providerId, null, realm, clientSession)); response = authenticationResponse.getResponse(); @@ -266,14 +272,16 @@ private Response handleResponse(String realmName, String providerId) { FederatedIdentity identity = authenticationResponse.getUser(); + if (!identityProviderConfig.isStoreToken()) { + identity.setToken(null); + } + return performLocalAuthentication(realm, providerId, identity, clientCode); } catch (Exception e) { if (session.getTransaction().isActive()) { session.getTransaction().rollback(); } - IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(realm, providerId); - return Flows.forms(session, realm, null, uriInfo).setError("Authentication failed. Could not authenticate against Identity Provider [" + identityProviderConfig.getName() + "].").createErrorPage(); } finally { if (session.getTransaction().isActive()) { 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 1917844f95a7..1e61b3e61394 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,36 +1,21 @@ package org.keycloak.services.resources.admin; import org.jboss.resteasy.annotations.cache.NoCache; -import org.jboss.resteasy.plugins.providers.multipart.InputPart; -import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; -import org.keycloak.broker.provider.IdentityProvider; -import org.keycloak.broker.provider.IdentityProviderFactory; -import org.keycloak.models.ClientModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.social.SocialIdentityProvider; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.services.resources.flows.Flows; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; -import javax.ws.rs.POST; import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static javax.ws.rs.core.Response.Status.BAD_REQUEST; /** * @author Pedro Igor @@ -39,132 +24,36 @@ public class IdentityProviderResource { private final RealmModel realm; private final KeycloakSession session; + private final IdentityProviderModel identityProviderModel; - public IdentityProviderResource(RealmModel realm, KeycloakSession session) { + public IdentityProviderResource(RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel) { this.realm = realm; this.session = session; + this.identityProviderModel = identityProviderModel; } @GET @NoCache @Produces("application/json") - public List getIdentityProviders() { - return realm.getIdentityProviders(); - } - - @Path("/providers/{provider_id}") - @GET - @NoCache - @Produces("application/json") - public Response getIdentityProviders(@PathParam("provider_id") String providerId) { - IdentityProviderFactory providerFactory = getProviderFactorytById(providerId); - - if (providerFactory != null) { - return Response.ok(providerFactory).build(); - } - - return Response.status(BAD_REQUEST).build(); - } - - @POST - @Consumes(MediaType.APPLICATION_JSON) - public Response create(@Context UriInfo uriInfo, IdentityProviderModel providerModel) { - realm.addIdentityProvider(providerModel); - - return Response.created(uriInfo.getAbsolutePathBuilder().path(providerModel.getProviderId()).build()).build(); - } - - @POST - @Consumes(MediaType.MULTIPART_FORM_DATA) - public Response createWithFile(@Context UriInfo uriInfo, MultipartFormDataInput input) throws IOException { - Map> formDataMap = input.getFormDataMap(); - - String id = formDataMap.get("id").get(0).getBodyAsString(); - String name = formDataMap.get("name").get(0).getBodyAsString(); - String providerId = formDataMap.get("providerId").get(0).getBodyAsString(); - String enabled = formDataMap.get("enabled").get(0).getBodyAsString(); - String updateProfileFirstLogin = formDataMap.get("updateProfileFirstLogin").get(0).getBodyAsString(); - String storeToken = formDataMap.get("storeToken").get(0).getBodyAsString(); - InputPart file = formDataMap.get("file").get(0); - InputStream inputStream = file.getBody(InputStream.class, null); - IdentityProviderFactory providerFactory = getProviderFactorytById(providerId); - Map config = providerFactory.parseConfig(inputStream); - IdentityProviderModel providerModel = new IdentityProviderModel(); - - providerModel.setId(id); - providerModel.setName(name); - providerModel.setProviderId(providerId); - providerModel.setEnabled(Boolean.valueOf(enabled)); - providerModel.setUpdateProfileFirstLogin(Boolean.valueOf(updateProfileFirstLogin)); - providerModel.setStoreToken(Boolean.valueOf(storeToken)); - providerModel.setConfig(config); - - return create(uriInfo, providerModel); - } - - @Path("{id}") - @GET - @NoCache - @Produces("application/json") - public Response getIdentityProvider(@PathParam("id") String providerId) { - for (IdentityProviderModel identityProviderModel : this.realm.getIdentityProviders()) { - if (identityProviderModel.getId().equals(providerId)) { - return Response.ok(identityProviderModel).build(); - } - } - - return Response.noContent().build(); + public IdentityProviderRepresentation getIdentityProvider() { + return ModelToRepresentation.toRepresentation(this.identityProviderModel); } - @Path("{id}") @DELETE @NoCache - public Response delete(@PathParam("id") String providerId) { - for (ClientModel applicationModel : getClientModels()) { - List allowedIdentityProviders = applicationModel.getAllowedIdentityProviders(); - - for (String appProvider : new ArrayList(allowedIdentityProviders)) { - if (appProvider.equals(providerId)) { - allowedIdentityProviders.remove(appProvider); - } - } - - applicationModel.updateAllowedIdentityProviders(allowedIdentityProviders); - } - - this.realm.removeIdentityProviderById(providerId); - + public Response delete() { + this.realm.removeIdentityProviderById(this.identityProviderModel.getId()); return Response.noContent().build(); } - private List getClientModels() { - List clients = new ArrayList(); - - clients.addAll(this.realm.getOAuthClients()); - clients.addAll(this.realm.getApplications()); - - return clients; - } - @PUT @Consumes("application/json") - public Response update(IdentityProviderModel model) { - this.realm.updateIdentityProvider(model); - return Response.noContent().build(); - } - - private IdentityProviderFactory getProviderFactorytById(String providerId) { - List allProviders = new ArrayList(); - - allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class)); - allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class)); - - for (ProviderFactory providerFactory : allProviders) { - if (providerFactory.getId().equals(providerId)) { - return (IdentityProviderFactory) providerFactory; - } + public Response update(IdentityProviderRepresentation model) { + try { + this.realm.updateIdentityProvider(RepresentationToModel.toModel(model)); + return Response.noContent().build(); + } catch (ModelDuplicateException e) { + return Flows.errors().exists("Identity Provider " + model.getId() + " already exists"); } - - return null; } } 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 new file mode 100755 index 000000000000..42c34974100c --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java @@ -0,0 +1,169 @@ +package org.keycloak.services.resources.admin; + +import org.jboss.resteasy.annotations.cache.NoCache; +import org.jboss.resteasy.plugins.providers.multipart.InputPart; +import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; +import org.jboss.resteasy.spi.NotFoundException; +import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.keycloak.broker.provider.IdentityProvider; +import org.keycloak.broker.provider.IdentityProviderFactory; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.services.resources.flows.Flows; +import org.keycloak.social.SocialIdentityProvider; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static javax.ws.rs.core.Response.Status.BAD_REQUEST; + +/** + * @author Pedro Igor + */ +public class IdentityProvidersResource { + + private final RealmModel realm; + private final KeycloakSession session; + private RealmAuth auth; + + public IdentityProvidersResource(RealmModel realm, KeycloakSession session, RealmAuth auth) { + this.realm = realm; + this.session = session; + this.auth = auth; + this.auth.init(RealmAuth.Resource.IDENTITY_PROVIDER); + } + + @GET + @NoCache + @Produces("application/json") + public List getIdentityProviders() { + this.auth.requireView(); + + List representations = new ArrayList(); + + for (IdentityProviderModel identityProviderModel : realm.getIdentityProviders()) { + representations.add(ModelToRepresentation.toRepresentation(identityProviderModel)); + } + + return representations; + } + + @Path("/providers/{provider_id}") + @GET + @NoCache + @Produces("application/json") + public Response getIdentityProviders(@PathParam("provider_id") String providerId) { + this.auth.requireView(); + IdentityProviderFactory providerFactory = getProviderFactorytById(providerId); + + if (providerFactory != null) { + return Response.ok(providerFactory).build(); + } + + return Response.status(BAD_REQUEST).build(); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + public Response create(@Context UriInfo uriInfo, IdentityProviderRepresentation representation) { + this.auth.requireManage(); + + try { + this.realm.addIdentityProvider(RepresentationToModel.toModel(representation)); + return Response.created(uriInfo.getAbsolutePathBuilder().path(representation.getProviderId()).build()).build(); + } catch (ModelDuplicateException e) { + return Flows.errors().exists("Identity Provider " + representation.getId() + " already exists"); + } + } + + @POST + @Consumes(MediaType.MULTIPART_FORM_DATA) + public Response createWithFile(@Context UriInfo uriInfo, MultipartFormDataInput input) throws IOException { + this.auth.requireManage(); + Map> formDataMap = input.getFormDataMap(); + + String id = formDataMap.get("id").get(0).getBodyAsString(); + String name = formDataMap.get("name").get(0).getBodyAsString(); + String providerId = formDataMap.get("providerId").get(0).getBodyAsString(); + String enabled = formDataMap.get("enabled").get(0).getBodyAsString(); + String updateProfileFirstLogin = formDataMap.get("updateProfileFirstLogin").get(0).getBodyAsString(); + String storeToken = formDataMap.get("storeToken").get(0).getBodyAsString(); + InputPart file = formDataMap.get("file").get(0); + InputStream inputStream = file.getBody(InputStream.class, null); + IdentityProviderFactory providerFactory = getProviderFactorytById(providerId); + Map config = providerFactory.parseConfig(inputStream); + IdentityProviderRepresentation representation = new IdentityProviderRepresentation(); + + representation.setId(id); + representation.setName(name); + representation.setProviderId(providerId); + representation.setEnabled(Boolean.valueOf(enabled)); + representation.setUpdateProfileFirstLogin(Boolean.valueOf(updateProfileFirstLogin)); + representation.setStoreToken(Boolean.valueOf(storeToken)); + representation.setConfig(config); + + return create(uriInfo, representation); + } + + @Path("{id}") + public IdentityProviderResource getIdentityProvider(@PathParam("id") String providerId) { + this.auth.requireView(); + IdentityProviderModel identityProviderModel = null; + + for (IdentityProviderModel storedIdentityProvider : this.realm.getIdentityProviders()) { + if (storedIdentityProvider.getId().equals(providerId) + || storedIdentityProvider.getInternalId().equals(providerId)) { + identityProviderModel = storedIdentityProvider; + } + } + + if (identityProviderModel == null) { + throw new NotFoundException("Could not find identity provider: " + providerId); + } + + IdentityProviderResource identityProviderResource = new IdentityProviderResource(realm, session, identityProviderModel); + ResteasyProviderFactory.getInstance().injectProperties(identityProviderResource); + + return identityProviderResource; + } + + private IdentityProviderFactory getProviderFactorytById(String providerId) { + List allProviders = getProviderFactories(); + + for (ProviderFactory providerFactory : allProviders) { + if (providerFactory.getId().equals(providerId)) { + return (IdentityProviderFactory) providerFactory; + } + } + + return null; + } + + private List getProviderFactories() { + List allProviders = new ArrayList(); + + allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class)); + allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class)); + + return allProviders; + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 51a39f79f898..3e5c2b0bd8ff 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -447,7 +447,7 @@ public Response testLDAPConnection(@QueryParam("action") String action, @QueryPa } @Path("identity-provider") - public IdentityProviderResource getIdentityProviderResource() { - return new IdentityProviderResource(realm, session); + public IdentityProvidersResource getIdentityProviderResource() { + return new IdentityProvidersResource(realm, session, this.auth); } } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java index 641315beb595..20238f1b0046 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAuth.java @@ -13,7 +13,7 @@ public class RealmAuth { private Resource resource; public enum Resource { - APPLICATION, CLIENT, USER, REALM, EVENTS + APPLICATION, CLIENT, USER, REALM, EVENTS, IDENTITY_PROVIDER } private AdminAuth auth; @@ -67,6 +67,8 @@ private String getViewRole(Resource resource) { return AdminRoles.VIEW_REALM; case EVENTS: return AdminRoles.VIEW_EVENTS; + case IDENTITY_PROVIDER: + return AdminRoles.VIEW_IDENTITY_PROVIDERS; default: throw new IllegalStateException(); } @@ -84,6 +86,8 @@ private String getManageRole(Resource resource) { return AdminRoles.MANAGE_REALM; case EVENTS: return AdminRoles.MANAGE_EVENTS; + case IDENTITY_PROVIDER: + return AdminRoles.MANAGE_IDENTITY_PROVIDERS; default: throw new IllegalStateException(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java index e0d29e980be7..5d04fa4de261 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java @@ -9,6 +9,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.ApplicationRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.OAuthClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.RealmManager; @@ -100,6 +101,8 @@ public static String name(Object o1) { return ((ApplicationRepresentation) o1).getName(); } else if (o1 instanceof OAuthClientRepresentation) { return ((OAuthClientRepresentation) o1).getName(); + } else if (o1 instanceof IdentityProviderRepresentation) { + return ((IdentityProviderRepresentation) o1).getId(); } throw new IllegalArgumentException(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java new file mode 100644 index 000000000000..2d91d9fb8cb3 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java @@ -0,0 +1,123 @@ +package org.keycloak.testsuite.admin; + +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.admin.client.resource.IdentityProviderResource; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.testsuite.rule.WebRule; + +import javax.ws.rs.NotFoundException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Stian Thorgersen + */ +public class IdentityProviderTest extends AbstractClientTest { + + @Rule + public WebRule webRule = new WebRule(this); + + @Test + public void testFindAll() { + realm.identityProviders().create(create("google", "google", "Google")); + realm.identityProviders().create(create("facebook", "facebook", "Facebook")); + + assertNames(realm.identityProviders().findAll(), "google", "facebook"); + } + + @Test + public void testCreate() { + IdentityProviderRepresentation newIdentityProvider = create("new-identity-provider", "oidc", "New Identity Provider"); + + newIdentityProvider.getConfig().put("clientId", "clientId"); + newIdentityProvider.getConfig().put("clientSecret", "clientSecret"); + + realm.identityProviders().create(newIdentityProvider); + IdentityProviderResource identityProviderResource = realm.identityProviders().get("new-identity-provider"); + + assertNotNull(identityProviderResource); + + IdentityProviderRepresentation representation = identityProviderResource.toRepresentation(); + + assertNotNull(representation); + + assertNotNull(representation.getInternalId()); + assertEquals("New Identity Provider", representation.getName()); + assertEquals("new-identity-provider", representation.getId()); + assertEquals("oidc", representation.getProviderId()); + assertEquals("clientId", representation.getConfig().get("clientId")); + assertEquals("clientSecret", representation.getConfig().get("clientSecret")); + assertTrue(representation.isEnabled()); + assertFalse(representation.isStoreToken()); + assertTrue(representation.isUpdateProfileFirstLogin()); + } + + @Test + public void testUpdate() { + IdentityProviderRepresentation newIdentityProvider = create("update-identity-provider", "oidc", "Update Identity Provider"); + + newIdentityProvider.getConfig().put("clientId", "clientId"); + newIdentityProvider.getConfig().put("clientSecret", "clientSecret"); + + realm.identityProviders().create(newIdentityProvider); + IdentityProviderResource identityProviderResource = realm.identityProviders().get("update-identity-provider"); + + assertNotNull(identityProviderResource); + + IdentityProviderRepresentation representation = identityProviderResource.toRepresentation(); + + assertNotNull(representation); + + assertEquals("update-identity-provider", representation.getId()); + + representation.setId("changed-alias"); + representation.setEnabled(false); + representation.setStoreToken(true); + representation.getConfig().put("clientId", "changedClientId"); + + identityProviderResource.update(representation); + + identityProviderResource = realm.identityProviders().get(representation.getInternalId()); + + assertNotNull(identityProviderResource); + + representation = identityProviderResource.toRepresentation(); + + assertFalse(representation.isEnabled()); + assertTrue(representation.isStoreToken()); + assertEquals("changedClientId", representation.getConfig().get("clientId")); + } + + @Test(expected = NotFoundException.class) + public void testRemove() { + IdentityProviderRepresentation newIdentityProvider = create("remove-identity-provider", "saml", "Remove Identity Provider"); + + realm.identityProviders().create(newIdentityProvider); + IdentityProviderResource identityProviderResource = realm.identityProviders().get("update-identity-provider"); + + assertNotNull(identityProviderResource); + + IdentityProviderRepresentation representation = identityProviderResource.toRepresentation(); + + assertNotNull(representation); + + identityProviderResource.remove(); + + realm.identityProviders().get("update-identity-provider"); + } + + private IdentityProviderRepresentation create(String id, String providerId, String name) { + IdentityProviderRepresentation identityProviderRepresentation = new IdentityProviderRepresentation(); + + identityProviderRepresentation.setId(id); + identityProviderRepresentation.setProviderId(providerId); + identityProviderRepresentation.setName(name); + identityProviderRepresentation.setEnabled(true); + + return identityProviderRepresentation; + } +}