diff --git a/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java b/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java new file mode 100644 index 0000000000..782669472c --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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; + +/** + * Configuration of KeyStore. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class KeyStoreConfig { + + protected Boolean realmCertificate; + protected String storePassword; + protected String keyPassword; + protected String keyAlias; + protected String realmAlias; + protected String format; + + public Boolean isRealmCertificate() { + return realmCertificate; + } + + public void setRealmCertificate(Boolean realmCertificate) { + this.realmCertificate = realmCertificate; + } + + public String getStorePassword() { + return storePassword; + } + + public void setStorePassword(String storePassword) { + this.storePassword = storePassword; + } + + public String getKeyPassword() { + return keyPassword; + } + + public void setKeyPassword(String keyPassword) { + this.keyPassword = keyPassword; + } + + public String getKeyAlias() { + return keyAlias; + } + + public void setKeyAlias(String keyAlias) { + this.keyAlias = keyAlias; + } + + public String getRealmAlias() { + return realmAlias; + } + + public void setRealmAlias(String realmAlias) { + this.realmAlias = realmAlias; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } +} diff --git a/integration/admin-client/pom.xml b/integration/admin-client/pom.xml index 00b5c27082..c0c88996bc 100755 --- a/integration/admin-client/pom.xml +++ b/integration/admin-client/pom.xml @@ -55,6 +55,11 @@ resteasy-client provided + + org.jboss.resteasy + resteasy-multipart-provider + provided + org.jboss.resteasy resteasy-jackson2-provider diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java new file mode 100644 index 0000000000..30d3bfb469 --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.admin.client.resource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; +import org.jboss.resteasy.annotations.cache.NoCache; +import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; +import org.keycloak.representations.KeyStoreConfig; +import org.keycloak.representations.idm.CertificateRepresentation; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public interface ClientAttributeCertificateResource { + + /** + * Get key info + * + * @return + */ + @GET + @NoCache + @Produces(MediaType.APPLICATION_JSON) + public CertificateRepresentation getKeyInfo(); + + /** + * Generate a new certificate with new key pair + * + * @return + */ + @POST + @NoCache + @Path("generate") + @Produces(MediaType.APPLICATION_JSON) + public CertificateRepresentation generate(); + + /** + * Upload certificate and eventually private key + * + * @param uriInfo + * @param input + * @return + */ + @POST + @Path("upload") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public CertificateRepresentation uploadJks(@Context final UriInfo uriInfo, MultipartFormDataInput input); + + /** + * Upload only certificate, not private key + * + * @param uriInfo + * @param input + * @return + */ + @POST + @Path("upload-certificate") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public CertificateRepresentation uploadJksCertificate(@Context final UriInfo uriInfo, MultipartFormDataInput input); + + /** + * Get a keystore file for the client, containing private key and public certificate + * + * @param config Keystore configuration as JSON + * @return + */ + @POST + @NoCache + @Path("/download") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.APPLICATION_JSON) + public byte[] getKeystore(final KeyStoreConfig config); + + /** + * Generate a new keypair and certificate, and get the private key file + * + * Generates a keypair and certificate and serves the private key in a specified keystore format. + * Only generated public certificate is saved in Keycloak DB - the private key is not. + * + * @param config Keystore configuration as JSON + * @return + */ + @POST + @NoCache + @Path("/generate-and-download") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.APPLICATION_JSON) + public byte[] generateAndGetKeystore(final KeyStoreConfig config); +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java index ee1c69ad26..644cc7a8e6 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java @@ -34,7 +34,6 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import java.util.List; import java.util.Map; -import java.util.Set; /** * @author rodrigo.sasaki@icarros.com.br @@ -55,21 +54,6 @@ public interface ClientResource { @DELETE public void remove(); - @GET - @Path("allowed-origins") - @Produces(MediaType.APPLICATION_JSON) - public Set getAllowedOrigins(); - - @PUT - @Path("allowed-origins") - @Consumes(MediaType.APPLICATION_JSON) - public void updateAllowedOrigins(Set allowedOrigins); - - @DELETE - @Path("allowed-origins") - @Consumes(MediaType.APPLICATION_JSON) - public void removeAllowedOrigins(Set originsToRemove); - @POST @Path("client-secret") @Produces(MediaType.APPLICATION_JSON) @@ -80,19 +64,31 @@ public interface ClientResource { @Produces(MediaType.APPLICATION_JSON) public CredentialRepresentation getSecret(); + /** + * Generate a new registration access token for the client + * + * @return + */ + @Path("registration-access-token") + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public ClientRepresentation regenerateRegistrationAccessToken(); + + /** + * Get representation of certificate resource + * + * @param attributePrefix + * @return + */ + @Path("certificates/{attr}") + public ClientAttributeCertificateResource getCertficateResource(@PathParam("attr") String attributePrefix); + @GET @NoCache @Path("installation/providers/{providerId}") public String getInstallationProvider(@PathParam("providerId") String providerId); - @POST - @Path("logout-all") - public void logoutAllUsers(); - - @POST - @Path("logout-user/{username}") - public void logoutUser(@PathParam("username") String username); - @Path("session-count") @GET @Produces(MediaType.APPLICATION_JSON) 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 15ca073e04..86548ecf84 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 @@ -149,7 +149,7 @@ public interface RealmResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response partialImport(PartialImportRepresentation rep); - + @Path("authentication") @Consumes(MediaType.APPLICATION_JSON) AuthenticationManagementResource flows(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java index 6633a68f20..bea79a018a 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java @@ -28,6 +28,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.KeyStoreConfig; import org.keycloak.representations.idm.CertificateRepresentation; import org.keycloak.services.ErrorResponseException; import org.keycloak.common.util.PemUtils; @@ -57,7 +58,7 @@ import java.util.Map; * @version $Revision: 1 $ */ public class ClientAttributeCertificateResource { - + public static final String PRIVATE_KEY = "private.key"; public static final String X509CERTIFICATE = "certificate"; @@ -112,7 +113,7 @@ public class ClientAttributeCertificateResource { client.setAttribute(privateAttribute, info.getPrivateKey()); client.setAttribute(certificateAttribute, info.getCertificate()); - + adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success(); return info; @@ -225,64 +226,6 @@ public class ClientAttributeCertificateResource { return info; } - - public static class KeyStoreConfig { - protected Boolean realmCertificate; - protected String storePassword; - protected String keyPassword; - protected String keyAlias; - protected String realmAlias; - protected String format; - - public Boolean isRealmCertificate() { - return realmCertificate; - } - - public void setRealmCertificate(Boolean realmCertificate) { - this.realmCertificate = realmCertificate; - } - - public String getStorePassword() { - return storePassword; - } - - public void setStorePassword(String storePassword) { - this.storePassword = storePassword; - } - - public String getKeyPassword() { - return keyPassword; - } - - public void setKeyPassword(String keyPassword) { - this.keyPassword = keyPassword; - } - - public String getKeyAlias() { - return keyAlias; - } - - public void setKeyAlias(String keyAlias) { - this.keyAlias = keyAlias; - } - - public String getRealmAlias() { - return realmAlias; - } - - public void setRealmAlias(String realmAlias) { - this.realmAlias = realmAlias; - } - - public String getFormat() { - return format; - } - - public void setFormat(String format) { - this.format = format; - } - } - /** * Get a keystore file for the client, containing private key and public certificate * diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 790dd2b8f6..09c3deec81 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -45,7 +45,6 @@ import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.ErrorResponse; -import org.keycloak.util.JsonSerialization; import org.keycloak.common.util.Time; import javax.ws.rs.Consumes; @@ -62,12 +61,11 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; + import static java.lang.Boolean.TRUE; @@ -172,54 +170,6 @@ public class ClientResource { return provider.generateInstallation(session, realm, client, keycloak.getBaseUri(uriInfo)); } - - /** - * Get keycloak.json file - * - * this method is deprecated, see getInstallationProvider - * - * Returns a keycloak.json file to be used to configure the adapter of the specified client. - * - * @return - * @throws IOException - */ - @Deprecated - @GET - @NoCache - @Path("installation/json") - @Produces(MediaType.APPLICATION_JSON) - public String getInstallation() throws IOException { - auth.requireView(); - - ClientManager clientManager = new ClientManager(new RealmManager(session)); - Object rep = clientManager.toInstallationRepresentation(realm, client, getKeycloakApplication().getBaseUri(uriInfo)); - - // TODO Temporary solution to pretty-print - return JsonSerialization.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rep); - } - - /** - * Get adapter configuration XML for JBoss / Wildfly Keycloak subsystem - * - * this method is deprecated, see getInstallationProvider - * - * Returns XML that can be included in the JBoss / Wildfly Keycloak subsystem to configure the adapter of that client. - * - * @return - * @throws IOException - */ - @Deprecated - @GET - @NoCache - @Path("installation/jboss") - @Produces(MediaType.TEXT_PLAIN) - public String getJBossInstallation() throws IOException { - auth.requireView(); - - ClientManager clientManager = new ClientManager(new RealmManager(session)); - return clientManager.toJBossSubsystemConfig(realm, client, getKeycloakApplication().getBaseUri(uriInfo)); - } - /** * Delete the client * @@ -306,64 +256,6 @@ public class ClientResource { return new RoleContainerResource(uriInfo, realm, auth, client, adminEvent); } - /** - * Get allowed origins - * - * This is used for CORS requests. Access tokens will have - * their allowedOrigins claim set to this value for tokens created for this client. - * - * @return - */ - @Path("allowed-origins") - @GET - @NoCache - @Produces(MediaType.APPLICATION_JSON) - public Set getAllowedOrigins() - { - auth.requireView(); - return client.getWebOrigins(); - } - - /** - * Update allowed origins - * - * This is used for CORS requests. Access tokens will have - * their allowedOrigins claim set to this value for tokens created for this client. - * - * @param allowedOrigins - */ - @Path("allowed-origins") - @PUT - @Consumes(MediaType.APPLICATION_JSON) - public void updateAllowedOrigins(Set allowedOrigins) - { - auth.requireManage(); - - client.setWebOrigins(allowedOrigins); - adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(client).success(); - } - - /** - * Delete the specified origins from current allowed origins - * - * This is used for CORS requests. Access tokens will have - * their allowedOrigins claim set to this value for tokens created for this client. - * - * @param allowedOrigins List of origins to delete - */ - @Path("allowed-origins") - @DELETE - @Consumes(MediaType.APPLICATION_JSON) - public void deleteAllowedOrigins(Set allowedOrigins) - { - auth.requireManage(); - - for (String origin : allowedOrigins) { - client.removeWebOrigin(origin); - } - adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); - } - /** * Get a user dedicated to the service account * @@ -507,41 +399,6 @@ public class ClientResource { return sessions; } - - /** - * Logout all sessions - * - * If the client has an admin URL, invalidate all sessions associated with that client directly. - * - */ - @Path("logout-all") - @POST - public GlobalRequestResult logoutAll() { - auth.requireManage(); - adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success(); - return new ResourceAdminManager(session).logoutClient(uriInfo.getRequestUri(), realm, client); - - } - - /** - * Logout the user by username - * - * If the client has an admin URL, invalidate the sessions for a particular user directly. - * - */ - @Path("logout-user/{username}") - @POST - public void logout(final @PathParam("username") String username) { - auth.requireManage(); - UserModel user = session.users().getUserByUsername(username, realm); - if (user == null) { - throw new NotFoundException("User not found"); - } - adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success(); - new ResourceAdminManager(session).logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user); - - } - /** * Register a cluster node with the client * diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java index 419ea1652b..4454ccbfe6 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java @@ -51,8 +51,14 @@ import org.keycloak.provider.Spi; import org.keycloak.representations.idm.ConfigPropertyRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation; -import org.keycloak.representations.info.*; import org.keycloak.broker.social.SocialIdentityProvider; +import org.keycloak.representations.info.ClientInstallationRepresentation; +import org.keycloak.representations.info.MemoryInfoRepresentation; +import org.keycloak.representations.info.ProviderRepresentation; +import org.keycloak.representations.info.ServerInfoRepresentation; +import org.keycloak.representations.info.SpiInfoRepresentation; +import org.keycloak.representations.info.SystemInfoRepresentation; +import org.keycloak.representations.info.ThemeInfoRepresentation; /** * @author Stian Thorgersen diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java index a027234c2b..0bce8a6780 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java @@ -23,22 +23,29 @@ import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.After; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; + import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.adapter.page.SessionPortal; + import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO; + import org.keycloak.testsuite.auth.page.account.Sessions; import org.keycloak.testsuite.auth.page.login.Login; + import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf; + import org.keycloak.testsuite.util.SecondBrowser; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; @@ -156,9 +163,11 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets public void testAdminApplicationLogout() { // login as bburke loginAndCheckSession(driver, testRealmLoginPage); + // logout mposolda with admin client - findClientResourceByClientId(testRealmResource(), "session-portal") - .logoutUser("mposolda"); + UserRepresentation mposolda = testRealmResource().users().search("mposolda", null, null, null, null, null).get(0); + testRealmResource().users().get(mposolda.getId()).logout(); + // bburke should be still logged with original httpSession in our browser window sessionPortalPage.navigateTo(); assertEquals(driver.getCurrentUrl(), sessionPortalPage.toString()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java index 7059318479..9913c1764c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java @@ -17,10 +17,12 @@ package org.keycloak.testsuite.admin.client; +import java.util.List; import javax.ws.rs.core.Response; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.AbstractAuthTest; import org.keycloak.testsuite.admin.ApiUtil; @@ -34,29 +36,35 @@ public abstract class AbstractClientTest extends AbstractAuthTest { return testRealmResource().toRepresentation(); } - protected void createOidcClient(String name) { + // returns UserRepresentation retrieved from server, with all fields, including id + protected UserRepresentation getFullUserRep(String userName) { + List results = testRealmResource().users().search(userName, null, null, null, null, null); + if (results.size() != 1) throw new RuntimeException("Did not find single user with username " + userName); + return results.get(0); + } + + protected String createOidcClient(String name) { ClientRepresentation clientRep = new ClientRepresentation(); clientRep.setClientId(name); clientRep.setName(name); clientRep.setRootUrl("foo"); clientRep.setProtocol("openid-connect"); - createClient(clientRep); + return createClient(clientRep); } - protected void createSamlClient(String name) { + protected String createSamlClient(String name) { ClientRepresentation clientRep = new ClientRepresentation(); clientRep.setClientId(name); clientRep.setName(name); clientRep.setProtocol("saml"); clientRep.setAdminUrl("samlEndpoint"); - createClient(clientRep); + return createClient(clientRep); } - protected void createClient(ClientRepresentation clientRep) { + protected String createClient(ClientRepresentation clientRep) { Response resp = testRealmResource().clients().create(clientRep); - // for some reason, findAll() will later fail unless readEntity is called here - resp.readEntity(String.class); - //testRealmResource().clients().findAll(); + resp.close(); + return ApiUtil.getCreatedId(resp); } protected ClientRepresentation findClientRepresentation(String name) { @@ -69,4 +77,8 @@ public abstract class AbstractClientTest extends AbstractAuthTest { return ApiUtil.findClientResourceByName(testRealmResource(), name); } + protected ClientResource findClientResourceById(String id) { + return ApiUtil.findClientResourceByClientId(testRealmResource(), id); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientProtocolMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientProtocolMapperTest.java new file mode 100644 index 0000000000..acf2329fba --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientProtocolMapperTest.java @@ -0,0 +1,266 @@ +/* + * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.testsuite.admin.client; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.core.Response; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.ProtocolMappersResource; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.testsuite.admin.ApiUtil; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class ClientProtocolMapperTest extends AbstractClientTest { + + private ClientResource oidcClientRsc; + private ProtocolMappersResource oidcMappersRsc; + private ClientResource samlClientRsc; + private ProtocolMappersResource samlMappersRsc; + + private Map> builtinMappers = null; + + @Before + public void init() { + createOidcClient("oidcMapperClient"); + oidcClientRsc = findClientResource("oidcMapperClient"); + oidcMappersRsc = oidcClientRsc.getProtocolMappers(); + + createSamlClient("samlMapperClient"); + samlClientRsc = findClientResource("samlMapperClient"); + samlMappersRsc = samlClientRsc.getProtocolMappers(); + + builtinMappers = adminClient.serverInfo().getInfo().getBuiltinProtocolMappers(); + } + + @After + public void tearDown() { + oidcClientRsc.remove(); + samlClientRsc.remove(); + } + + private ProtocolMapperRepresentation makeMapper(String protocol, String name, String mapperType, Map config) { + ProtocolMapperRepresentation rep = new ProtocolMapperRepresentation(); + rep.setProtocol(protocol); + rep.setName(name); + rep.setProtocolMapper(mapperType); + rep.setConfig(config); + rep.setConsentRequired(true); + rep.setConsentText("Test Consent Text"); + return rep; + } + + private ProtocolMapperRepresentation makeSamlMapper(String name) { + Map config = new HashMap<>(); + config.put("role", "account.view-profile"); + config.put("new.role.name", "new-role-name"); + return makeMapper("saml", name, "saml-role-name-mapper", config); + } + + private ProtocolMapperRepresentation makeOidcMapper(String name) { + Map config = new HashMap<>(); + config.put("role", "myrole"); + return makeMapper("openid-connect", name, "oidc-hardcoded-role-mapper", config); + } + + private void assertEqualMappers(ProtocolMapperRepresentation original, ProtocolMapperRepresentation created) { + assertNotNull(created); + assertEquals(original.getName(), created.getName()); + assertEquals(original.getConfig(), created.getConfig()); + assertEquals(original.getConsentText(), created.getConsentText()); + assertEquals(original.isConsentRequired(), created.isConsentRequired()); + assertEquals(original.getProtocol(), created.getProtocol()); + assertEquals(original.getProtocolMapper(), created.getProtocolMapper()); + } + + @Test + public void testGetMappersList() { + assertFalse(oidcMappersRsc.getMappers().isEmpty()); + assertFalse(samlMappersRsc.getMappers().isEmpty()); + } + + private boolean containsMapper(List mappers, ProtocolMapperRepresentation mapper) { + for (ProtocolMapperRepresentation listedMapper : mappers) { + if (listedMapper.getName().equals(mapper.getName())) return true; + } + + return false; + } + + private List mappersToAdd(List oldMappers, + List builtins) { + List mappersToAdd = new ArrayList<>(); + for (ProtocolMapperRepresentation builtin : builtins) { + if (!containsMapper(oldMappers, builtin)) mappersToAdd.add(builtin); + } + + return mappersToAdd; + } + + private void testAddAllBuiltinMappers(ProtocolMappersResource resource, String resourceName) { + List oldMappers = resource.getMappersPerProtocol(resourceName); + List builtins = builtinMappers.get(resourceName); + + List mappersToAdd = mappersToAdd(oldMappers, builtins); + + // This is used by admin console to add builtin mappers + resource.createMapper(mappersToAdd); + + List newMappers = resource.getMappersPerProtocol(resourceName); + assertEquals(oldMappers.size() + mappersToAdd.size(), newMappers.size()); + + for (ProtocolMapperRepresentation rep : mappersToAdd) { + assertTrue(containsMapper(newMappers, rep)); + } + } + + @Test + public void testCreateOidcMappersFromList() { + testAddAllBuiltinMappers(oidcMappersRsc, "openid-connect"); + } + + @Test + public void testCreateSamlMappersFromList() { + testAddAllBuiltinMappers(samlMappersRsc, "saml"); + } + + @Test + public void testCreateSamlProtocolMapper() { + + //{"protocol":"saml", + // "config":{"role":"account.view-profile","new.role.name":"new-role-name"}, + // "consentRequired":true, + // "consentText":"My consent text", + // "name":"saml-role-name-maper", + // "protocolMapper":"saml-role-name-mapper"} + ProtocolMapperRepresentation rep = makeSamlMapper("saml-role-name-mapper"); + + int totalMappers = samlMappersRsc.getMappers().size(); + int totalSamlMappers = samlMappersRsc.getMappersPerProtocol("saml").size(); + Response resp = samlMappersRsc.createMapper(rep); + resp.close(); + assertEquals(totalMappers + 1, samlMappersRsc.getMappers().size()); + assertEquals(totalSamlMappers + 1, samlMappersRsc.getMappersPerProtocol("saml").size()); + + String createdId = ApiUtil.getCreatedId(resp); + ProtocolMapperRepresentation created = samlMappersRsc.getMapperById(createdId); + assertEqualMappers(rep, created); + } + + @Test + public void testCreateOidcProtocolMapper() { + //{"protocol":"openid-connect", + // "config":{"role":"myrole"}, + // "consentRequired":true, + // "consentText":"My consent text", + // "name":"oidc-hardcoded-role-mapper", + // "protocolMapper":"oidc-hardcoded-role-mapper"} + ProtocolMapperRepresentation rep = makeOidcMapper("oidc-hardcoded-role-mapper"); + + int totalMappers = oidcMappersRsc.getMappers().size(); + int totalOidcMappers = oidcMappersRsc.getMappersPerProtocol("openid-connect").size(); + Response resp = oidcMappersRsc.createMapper(rep); + resp.close(); + assertEquals(totalMappers + 1, oidcMappersRsc.getMappers().size()); + assertEquals(totalOidcMappers + 1, oidcMappersRsc.getMappersPerProtocol("openid-connect").size()); + + String createdId = ApiUtil.getCreatedId(resp); + ProtocolMapperRepresentation created = oidcMappersRsc.getMapperById(createdId);//findByName(samlMappersRsc, "saml-role-name-mapper"); + assertEqualMappers(rep, created); + } + + @Test + public void testUpdateSamlMapper() { + ProtocolMapperRepresentation rep = makeSamlMapper("saml-role-name-mapper2"); + + Response resp = samlMappersRsc.createMapper(rep); + resp.close(); + + String createdId = ApiUtil.getCreatedId(resp); + + rep.getConfig().put("role", "account.manage-account"); + rep.setId(createdId); + rep.setConsentRequired(false); + samlMappersRsc.update(createdId, rep); + + ProtocolMapperRepresentation updated = samlMappersRsc.getMapperById(createdId); + assertEqualMappers(rep, updated); + } + + @Test + public void testUpdateOidcMapper() { + ProtocolMapperRepresentation rep = makeOidcMapper("oidc-hardcoded-role-mapper2"); + + Response resp = oidcMappersRsc.createMapper(rep); + resp.close(); + + String createdId = ApiUtil.getCreatedId(resp); + + rep.getConfig().put("role", "myotherrole"); + rep.setId(createdId); + rep.setConsentRequired(false); + oidcMappersRsc.update(createdId, rep); + + ProtocolMapperRepresentation updated = oidcMappersRsc.getMapperById(createdId); + assertEqualMappers(rep, updated); + } + + @Test (expected = NotFoundException.class) + public void testDeleteSamlMapper() { + ProtocolMapperRepresentation rep = makeSamlMapper("saml-role-name-mapper3"); + + Response resp = samlMappersRsc.createMapper(rep); + resp.close(); + + String createdId = ApiUtil.getCreatedId(resp); + + samlMappersRsc.delete(createdId); + + samlMappersRsc.getMapperById(createdId); + } + + @Test (expected = NotFoundException.class) + public void testDeleteOidcMapper() { + ProtocolMapperRepresentation rep = makeOidcMapper("oidc-hardcoded-role-mapper3"); + + Response resp = oidcMappersRsc.createMapper(rep); + resp.close(); + + String createdId = ApiUtil.getCreatedId(resp); + + oidcMappersRsc.delete(createdId); + + oidcMappersRsc.getMapperById(createdId); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientRolesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientRolesTest.java index 9180f15242..5fb7c7dd0b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientRolesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientRolesTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.admin.client; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.keycloak.admin.client.resource.ClientResource; @@ -42,16 +43,17 @@ public class ClientRolesTest extends AbstractClientTest { rolesRsc = clientRsc.roles(); } + @After + public void tearDown() { + clientRsc.remove(); + } + private RoleRepresentation makeRole(String name) { RoleRepresentation role = new RoleRepresentation(); role.setName(name); return role; } - /* private boolean hasRole(RolesResource rolesRsc, String name) { - return rolesRsc.get(name) != null; - }*/ - private boolean hasRole(RolesResource rolesRsc, String name) { for (RoleRepresentation role : rolesRsc.list()) { if (role.getName().equals(name)) return true; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java new file mode 100644 index 0000000000..2b79e50af9 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.testsuite.admin.client; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.ClientAttributeCertificateResource; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.representations.idm.CertificateRepresentation; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class CredentialsTest extends AbstractClientTest { + + private ClientResource accountClient; + + @Before + public void init() { + accountClient = findClientResourceById("account"); + } + + @Test + public void testGetAndRegenerateSecret() { + CredentialRepresentation oldCredential = accountClient.getSecret(); + CredentialRepresentation newCredential = accountClient.generateNewSecret(); + assertNotNull(oldCredential); + assertNotNull(newCredential); + assertNotEquals(newCredential.getValue(), oldCredential.getValue()); + assertEquals(newCredential.getValue(), accountClient.getSecret().getValue()); + } + + @Test + public void testGetAndRegenerateRegistrationAccessToken() { + ClientRepresentation rep = accountClient.toRepresentation(); + String oldToken = rep.getRegistrationAccessToken(); + String newToken = accountClient.regenerateRegistrationAccessToken().getRegistrationAccessToken(); + assertNull(oldToken); // registration access token not saved in ClientRep + assertNotNull(newToken); // it's only available via regenerateRegistrationAccessToken() + assertNull(accountClient.toRepresentation().getRegistrationAccessToken()); + } + + @Test + public void testGetCertificateResource() { + ClientAttributeCertificateResource certRsc = accountClient.getCertficateResource("jwt.credential"); + CertificateRepresentation cert = certRsc.generate(); + CertificateRepresentation certFromGet = certRsc.getKeyInfo(); + assertEquals(cert.getCertificate(), certFromGet.getCertificate()); + assertEquals(cert.getPrivateKey(), certFromGet.getPrivateKey()); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java index 86927e468a..71371988aa 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.admin.client; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.keycloak.admin.client.resource.ClientResource; @@ -47,6 +48,12 @@ public class InstallationTest extends AbstractClientTest { samlClient = findClientResource(SAML_NAME); } + @After + public void tearDown() { + oidcClient.remove(); + samlClient.remove(); + } + private String authServerUrl() { return AuthServerTestEnricher.getAuthServerContextRoot() + "/auth"; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/SessionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/SessionTest.java new file mode 100644 index 0000000000..cf15f960a9 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/SessionTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.testsuite.admin.client; + +import java.util.List; +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; +import org.keycloak.testsuite.auth.page.account.AccountManagement; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class SessionTest extends AbstractClientTest { + private static boolean testUserCreated = false; + + @Page + protected AccountManagement testRealmAccountManagementPage; + + @Before + public void init() { + // make user test user exists in test realm + if (!testUserCreated) createTestUserWithAdminClient(); + testUserCreated = true; + } + + @Test + public void testGetAppSessionCount() { + ClientResource accountClient = findClientResourceById("account"); + int sessionCount = accountClient.getApplicationSessionCount().get("count"); + assertEquals(0, sessionCount); + + testRealmAccountManagementPage.navigateTo(); + loginPage.form().login(testUser); + + sessionCount = accountClient.getApplicationSessionCount().get("count"); + assertEquals(1, sessionCount); + + testRealmAccountManagementPage.signOut(); + + sessionCount = accountClient.getApplicationSessionCount().get("count"); + assertEquals(0, sessionCount); + } + + @Test + public void testGetUserSessions() { + //List> stats = this.testRealmResource().getClientSessionStats(); + ClientResource account = findClientResourceById("account"); + + testRealmAccountManagementPage.navigateTo(); + loginPage.form().login(testUser); + + List sessions = account.getUserSessions(0, 5); + assertEquals(1, sessions.size()); + + UserSessionRepresentation rep = sessions.get(0); + + UserRepresentation testUserRep = getFullUserRep(testUser.getUsername()); + assertEquals(testUserRep.getId(), rep.getUserId()); + assertEquals(testUserRep.getUsername(), rep.getUsername()); + + String clientId = account.toRepresentation().getId(); + assertEquals("account", rep.getClients().get(clientId)); + assertNotNull(rep.getIpAddress()); + assertNotNull(rep.getLastAccess()); + assertNotNull(rep.getStart()); + + testRealmAccountManagementPage.signOut(); + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index 7b0d9d3891..7097588b9b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -59,6 +59,7 @@ import java.net.URI; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.keycloak.representations.idm.UserRepresentation; /** * Tests Undertow Adapter @@ -598,7 +599,8 @@ public class AdapterTestStrategy extends ExternalResource { // logout mposolda with admin client Keycloak keycloakAdmin = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID); - ApiUtil.findClientByClientId(keycloakAdmin.realm("demo"), "session-portal").logoutUser("mposolda"); + UserRepresentation mposolda = keycloakAdmin.realm("demo").users().search("mposolda", null, null, null, null, null).get(0); + keycloakAdmin.realm("demo").users().get(mposolda.getId()).logout(); // bburke should be still logged with original httpSession in our browser window driver.navigate().to(APP_SERVER_BASE_URL + "/session-portal"); diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index 1ec8513972..868ec4d2b0 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -952,19 +952,6 @@ module.factory('ClientOfflineSessions', function($resource) { }); }); -module.factory('ClientLogoutAll', function($resource) { - return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-all', { - realm : '@realm', - client : "@client" - }); -}); -module.factory('ClientLogoutUser', function($resource) { - return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-user/:user', { - realm : '@realm', - client : "@client", - user : "@user" - }); -}); module.factory('RealmLogoutAll', function($resource) { return $resource(authUrl + '/admin/realms/:realm/logout-all', { realm : '@realm'