KEYCLOAK-2535: ClientResource endpoint tests
This commit is contained in:
parent
8a4a1660a4
commit
cf10a09008
15 changed files with 683 additions and 257 deletions
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -55,6 +55,11 @@
|
|||
<artifactId>resteasy-client</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-multipart-provider</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jackson2-provider</artifactId>
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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<String> getAllowedOrigins();
|
||||
|
||||
@PUT
|
||||
@Path("allowed-origins")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void updateAllowedOrigins(Set<String> allowedOrigins);
|
||||
|
||||
@DELETE
|
||||
@Path("allowed-origins")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void removeAllowedOrigins(Set<String> 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)
|
||||
|
|
|
@ -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;
|
||||
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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<String> 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<String> 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<String> 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
|
||||
*
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<UserRepresentation> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* 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.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<String, List<ProtocolMapperRepresentation>> builtinMappers;
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
private ProtocolMapperRepresentation makeMapper(String protocol, String name, String mapperType, Map<String, String> 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<String, String> 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<String, String> 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<ProtocolMapperRepresentation> mappers, ProtocolMapperRepresentation mapper) {
|
||||
for (ProtocolMapperRepresentation listedMapper : mappers) {
|
||||
if (listedMapper.getName().equals(mapper.getName())) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ProtocolMapperRepresentation> mappersToAdd(List<ProtocolMapperRepresentation> oldMappers,
|
||||
List<ProtocolMapperRepresentation> builtins) {
|
||||
List<ProtocolMapperRepresentation> mappersToAdd = new ArrayList<>();
|
||||
for (ProtocolMapperRepresentation builtin : builtins) {
|
||||
if (!containsMapper(oldMappers, builtin)) mappersToAdd.add(builtin);
|
||||
}
|
||||
|
||||
return mappersToAdd;
|
||||
}
|
||||
|
||||
private void testAddAllBuiltinMappers(ProtocolMappersResource resource, String resourceName) {
|
||||
List<ProtocolMapperRepresentation> oldMappers = resource.getMappersPerProtocol(resourceName);
|
||||
List<ProtocolMapperRepresentation> builtins = builtinMappers.get(resourceName);
|
||||
|
||||
List<ProtocolMapperRepresentation> mappersToAdd = mappersToAdd(oldMappers, builtins);
|
||||
|
||||
// This is used by admin console to add builtin mappers
|
||||
resource.createMapper(mappersToAdd);
|
||||
|
||||
List<ProtocolMapperRepresentation> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -48,10 +48,6 @@ public class ClientRolesTest extends AbstractClientTest {
|
|||
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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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<java.util.Map<String, String>> stats = this.testRealmResource().getClientSessionStats();
|
||||
ClientResource account = findClientResourceById("account");
|
||||
|
||||
testRealmAccountManagementPage.navigateTo();
|
||||
loginPage.form().login(testUser);
|
||||
|
||||
List<UserSessionRepresentation> 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();
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue