KEYCLOAK-10063 Merge preview features test: ClientTokenExchangeTest

This commit is contained in:
Martin Bartos RH 2019-04-12 16:51:28 +02:00 committed by Marek Posolda
parent 0042726dd8
commit a6e53b3f1c
3 changed files with 210 additions and 81 deletions

View file

@ -20,6 +20,7 @@ package org.keycloak.testsuite.rest;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.common.Profile;
import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
import org.keycloak.events.Event;
@ -278,18 +279,18 @@ public class TestingResourceProvider implements RealmResourceProvider {
/**
* Query events
*
* <p>
* Returns all events, or filters them based on URL query parameters listed here
*
* @param realmId The realm
* @param types The types of events to return
* @param client App or oauth client name
* @param user User id
* @param dateFrom From date
* @param dateTo To date
* @param ipAddress IP address
* @param realmId The realm
* @param types The types of events to return
* @param client App or oauth client name
* @param user User id
* @param dateFrom From date
* @param dateTo To date
* @param ipAddress IP address
* @param firstResult Paging offset
* @param maxResults Paging size
* @param maxResults Paging size
* @return
*/
@Path("query-events")
@ -297,9 +298,9 @@ public class TestingResourceProvider implements RealmResourceProvider {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<EventRepresentation> queryEvents(@QueryParam("realmId") String realmId, @QueryParam("type") List<String> types, @QueryParam("client") String client,
@QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults) {
@QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
@ -325,12 +326,12 @@ public class TestingResourceProvider implements RealmResourceProvider {
query.user(user);
}
if(dateFrom != null) {
if (dateFrom != null) {
Date from = formatDate(dateFrom, "Date(From)");
query.fromDate(from);
}
if(dateTo != null) {
if (dateTo != null) {
Date to = formatDate(dateTo, "Date(To)");
query.toDate(to);
}
@ -429,10 +430,10 @@ public class TestingResourceProvider implements RealmResourceProvider {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<AdminEventRepresentation> getAdminEvents(@QueryParam("realmId") String realmId, @QueryParam("operationTypes") List<String> operationTypes, @QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
@QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
@QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults) {
@QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
@QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults) {
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
AdminEventQuery query = eventStore.createAdminQuery();
@ -469,12 +470,12 @@ public class TestingResourceProvider implements RealmResourceProvider {
query.operation(t);
}
if(dateFrom != null) {
if (dateFrom != null) {
Date from = formatDate(dateFrom, "Date(From)");
query.fromTime(from);
}
if(dateTo != null) {
if (dateTo != null) {
Date to = formatDate(dateTo, "Date(To)");
query.toTime(to);
}
@ -618,7 +619,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
public UserRepresentation getUserByUsernameFromFedProviderFactory(@QueryParam("realmName") String realmName,
@QueryParam("userName") String userName) {
RealmModel realm = getRealmByName(realmName);
DummyUserFederationProviderFactory factory = (DummyUserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, "dummy");
DummyUserFederationProviderFactory factory = (DummyUserFederationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, "dummy");
UserModel user = factory.create(session, null).getUserByUsername(userName, realm);
if (user == null) return null;
return ModelToRepresentation.toRepresentation(session, realm, user);
@ -768,7 +769,6 @@ public class TestingResourceProvider implements RealmResourceProvider {
}
@POST
@Path("/run-on-server")
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
@ -824,6 +824,62 @@ public class TestingResourceProvider implements RealmResourceProvider {
return new TestJavascriptResource();
}
@POST
@Path("/enable-feature/{feature}")
@Consumes(MediaType.APPLICATION_JSON)
public Response enableFeature(@PathParam("feature") String feature) {
Profile.Feature featureProfile = Profile.Feature.valueOf(feature);
if (featureProfile == null)
return Response.status(Response.Status.NOT_FOUND).build();
if (Profile.isFeatureEnabled(featureProfile))
return Response.ok().build();
System.setProperty("keycloak.profile.feature." + feature.toLowerCase(), "enabled");
switch (featureProfile.getType()) {
case PREVIEW:
Profile.getPreviewFeatures().add(featureProfile);
break;
case EXPERIMENTAL:
Profile.getExperimentalFeatures().add(featureProfile);
break;
}
Profile.getDisabledFeatures().remove(featureProfile);
Profile.init();
if (Profile.isFeatureEnabled(featureProfile))
return Response.ok().build();
else
return Response.status(Response.Status.NOT_FOUND).build();
}
@POST
@Path("/disable-feature/{feature}")
@Consumes(MediaType.APPLICATION_JSON)
public Response disableFeature(@PathParam("feature") String feature) {
Profile.Feature featureProfile = Profile.Feature.valueOf(feature);
if (featureProfile == null)
return Response.status(Response.Status.NOT_FOUND).build();
if (!Profile.isFeatureEnabled(featureProfile))
return Response.ok().build();
System.getProperties().remove("keycloak.profile.feature." + feature.toLowerCase());
Profile.getDisabledFeatures().add(featureProfile);
Profile.init();
if (!Profile.isFeatureEnabled(featureProfile))
return Response.ok().build();
else
return Response.status(Response.Status.NOT_FOUND).build();
}
private RealmModel getRealmByName(String realmName) {
RealmProvider realmProvider = session.getProvider(RealmProvider.class);
RealmModel realm = realmProvider.getRealmByName(realmName);

View file

@ -98,15 +98,15 @@ public interface TestingResource {
*
* Returns all events, or filters them based on URL query parameters listed here
*
* @param realmId The realm
* @param types The types of events to return
* @param client App or oauth client name
* @param user User id
* @param dateFrom From date
* @param dateTo To date
* @param ipAddress IP address
* @param realmId The realm
* @param types The types of events to return
* @param client App or oauth client name
* @param user User id
* @param dateFrom From date
* @param dateTo To date
* @param ipAddress IP address
* @param firstResult Paging offset
* @param maxResults Paging size
* @param maxResults Paging size
* @return
*/
@Path("query-events")
@ -140,7 +140,7 @@ public interface TestingResource {
/**
* Get admin events
*
* <p>
* Returns all admin events, or filters events based on URL query parameters listed here
*
* @param realmId
@ -211,7 +211,7 @@ public interface TestingResource {
@Path("/update-pass-through-auth-state")
@Produces(MediaType.APPLICATION_JSON)
AuthenticatorState updateAuthenticator(AuthenticatorState state);
@GET
@Path("/valid-credentials")
@Produces(MediaType.APPLICATION_JSON)
@ -312,4 +312,13 @@ public interface TestingResource {
@Produces(MediaType.TEXT_HTML_UTF_8)
String getJavascriptTestingEnvironment();
@POST
@Path("/enable-feature/{feature}")
@Consumes(MediaType.APPLICATION_JSON)
Response enableFeature(@PathParam("feature") String feature);
@POST
@Path("/disable-feature/{feature}")
@Consumes(MediaType.APPLICATION_JSON)
Response disableFeature(@PathParam("feature") String feature);
}

View file

@ -19,6 +19,8 @@ package org.keycloak.testsuite.oauth;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
@ -26,15 +28,8 @@ import org.keycloak.TokenVerifier;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.common.Profile;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ImpersonationConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.*;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
@ -47,6 +42,7 @@ import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.util.BasicAuthHelper;
@ -62,7 +58,7 @@ import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_ID;
import static org.keycloak.models.ImpersonationSessionNote.IMPERSONATOR_USERNAME;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
@ -72,6 +68,8 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
*/
public class ClientTokenExchangeTest extends AbstractKeycloakTest {
private final Profile.Feature FEATURE = Profile.Feature.TOKEN_EXCHANGE;
@Rule
public AssertEvents events = new AssertEvents(this);
@ -80,6 +78,30 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
return RunOnServerDeployment.create(ClientTokenExchangeTest.class);
}
@Before
public void enableFeature() {
// Required feature should return Status code 501 - Feature doesn't work
testingClient.server().run(ClientTokenExchangeTest::addDirectExchanger);
Assert.assertEquals(501, checkTokenExchange().getStatus());
testingClient.server().run(ClientTokenExchangeTest::removeDirectExchanger);
// Test if required feature is enabled in Profiles.
Response response = testingClient.testing().enableFeature(FEATURE.toString());
Assert.assertEquals(200, response.getStatus());
// Test if the required feature really works.
testingClient.server().run(ClientTokenExchangeTest::addDirectExchanger);
Assert.assertEquals(200, checkTokenExchange().getStatus());
testingClient.server().run(ClientTokenExchangeTest::removeDirectExchanger);
}
@After
public void disableFeature() {
// Test if required feature is disabled.
Response response = testingClient.testing().disableFeature(FEATURE.toString());
Assert.assertEquals(200, response.getStatus());
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation testRealmRep = new RealmRepresentation();
@ -90,20 +112,14 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
}
public static void setupRealm(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName(TEST);
addDirectExchanger(session);
RoleModel exampleRole = realm.addRole("example");
RealmModel realm = session.realms().getRealmByName(TEST);
RoleModel exampleRole = realm.getRole("example");
AdminPermissionManagement management = AdminPermissions.management(session, realm);
ClientModel target = realm.addClient("target");
target.setDirectAccessGrantsEnabled(true);
target.setEnabled(true);
target.setSecret("secret");
target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
target.setFullScopeAllowed(false);
target.addScopeMapping(exampleRole);
ClientModel target = realm.getClientByClientId("target");
assertNotNull(target);
RoleModel impersonateRole = management.getRealmManagementClient().getRole(ImpersonationConstants.IMPERSONATION_ROLE);
@ -119,16 +135,6 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
clientExchanger.addProtocolMapper(UserSessionNoteMapper.createUserSessionNoteMapper(IMPERSONATOR_ID));
clientExchanger.addProtocolMapper(UserSessionNoteMapper.createUserSessionNoteMapper(IMPERSONATOR_USERNAME));
ClientModel directExchanger = realm.addClient("direct-exchanger");
directExchanger.setClientId("direct-exchanger");
directExchanger.setPublicClient(false);
directExchanger.setDirectAccessGrantsEnabled(true);
directExchanger.setEnabled(true);
directExchanger.setSecret("secret");
directExchanger.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
directExchanger.setFullScopeAllowed(false);
ClientModel illegal = realm.addClient("illegal");
illegal.setClientId("illegal");
illegal.setPublicClient(false);
@ -172,14 +178,13 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
directNoSecret.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
directNoSecret.setFullScopeAllowed(false);
// permission for client to client exchange to "target" client
management.clients().setPermissionsEnabled(target, true);
ClientPolicyRepresentation clientRep = new ClientPolicyRepresentation();
clientRep.setName("to");
clientRep.addClient(clientExchanger.getId());
clientRep.addClient(legal.getId());
clientRep.addClient(directLegal.getId());
ResourceServer server = management.realmResourceServer();
Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(clientRep, server);
management.clients().exchangeToPermission(target).addAssociatedPolicy(clientPolicy);
@ -189,7 +194,6 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
ClientPolicyRepresentation clientImpersonateRep = new ClientPolicyRepresentation();
clientImpersonateRep.setName("clientImpersonators");
clientImpersonateRep.addClient(directLegal.getId());
clientImpersonateRep.addClient(directExchanger.getId());
clientImpersonateRep.addClient(directPublic.getId());
clientImpersonateRep.addClient(directNoSecret.getId());
server = management.realmResourceServer();
@ -207,12 +211,6 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
UserModel bad = session.users().addUser(realm, "bad-impersonator");
bad.setEnabled(true);
session.userCredentialManager().updateCredential(realm, bad, UserCredentialModel.password("password"));
UserModel impersonatedUser = session.users().addUser(realm, "impersonated-user");
impersonatedUser.setEnabled(true);
session.userCredentialManager().updateCredential(realm, impersonatedUser, UserCredentialModel.password("password"));
impersonatedUser.grantRole(exampleRole);
}
@Override
@ -220,8 +218,8 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
return true;
}
@Test
@UncaughtServerErrorExpected
public void testExchange() throws Exception {
ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE);
@ -229,7 +227,6 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
oauth.realm(TEST);
oauth.clientId("client-exchanger");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "user", "password");
String accessToken = response.getAccessToken();
TokenVerifier<AccessToken> accessTokenVerifier = TokenVerifier.create(accessToken, AccessToken.class);
@ -239,7 +236,6 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
{
response = oauth.doTokenExchange(TEST, accessToken, "target", "client-exchanger", "secret");
String exchangedTokenString = response.getAccessToken();
TokenVerifier<AccessToken> verifier = TokenVerifier.create(exchangedTokenString, AccessToken.class);
AccessToken exchangedToken = verifier.parse().getToken();
@ -265,7 +261,9 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
Assert.assertEquals(403, response.getStatusCode());
}
}
@Test
@UncaughtServerErrorExpected
public void testImpersonation() throws Exception {
ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE);
@ -346,11 +344,10 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
Assert.assertEquals(exchangedToken.getPreferredUsername(), "impersonated-user");
Assert.assertTrue(exchangedToken.getRealmAccess().isUserInRole("example"));
}
}
@Test
@UncaughtServerErrorExpected
public void testBadImpersonator() throws Exception {
ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE);
@ -394,6 +391,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
}
@Test
@UncaughtServerErrorExpected
public void testDirectImpersonation() throws Exception {
ProfileAssume.assumeFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE);
@ -416,7 +414,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
.param(OAuth2Constants.REQUESTED_SUBJECT, "impersonated-user")
));
org.junit.Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse accessTokenResponse = response.readEntity(AccessTokenResponse.class);
response.close();
@ -440,7 +438,7 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
.param(OAuth2Constants.AUDIENCE, "target")
));
org.junit.Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(200, response.getStatus());
AccessTokenResponse accessTokenResponse = response.readEntity(AccessTokenResponse.class);
response.close();
@ -482,9 +480,75 @@ public class ClientTokenExchangeTest extends AbstractKeycloakTest {
Assert.assertTrue(response.getStatus() >= 400);
response.close();
}
}
private static void addDirectExchanger(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName(TEST);
RoleModel exampleRole = realm.addRole("example");
AdminPermissionManagement management = AdminPermissions.management(session, realm);
}
ClientModel target = realm.addClient("target");
target.setName("target");
target.setClientId("target");
target.setDirectAccessGrantsEnabled(true);
target.setEnabled(true);
target.setSecret("secret");
target.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
target.setFullScopeAllowed(false);
target.addScopeMapping(exampleRole);
ClientModel directExchanger = realm.addClient("direct-exchanger");
directExchanger.setName("direct-exchanger");
directExchanger.setClientId("direct-exchanger");
directExchanger.setPublicClient(false);
directExchanger.setDirectAccessGrantsEnabled(true);
directExchanger.setEnabled(true);
directExchanger.setSecret("secret");
directExchanger.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
directExchanger.setFullScopeAllowed(false);
// permission for client to client exchange to "target" client
management.clients().setPermissionsEnabled(target, true);
ClientPolicyRepresentation clientImpersonateRep = new ClientPolicyRepresentation();
clientImpersonateRep.setName("clientImpersonatorsDirect");
clientImpersonateRep.addClient(directExchanger.getId());
ResourceServer server = management.realmResourceServer();
Policy clientImpersonatePolicy = management.authz().getStoreFactory().getPolicyStore().create(clientImpersonateRep, server);
management.users().setPermissionsEnabled(true);
management.users().adminImpersonatingPermission().addAssociatedPolicy(clientImpersonatePolicy);
management.users().adminImpersonatingPermission().setDecisionStrategy(DecisionStrategy.AFFIRMATIVE);
UserModel impersonatedUser = session.users().addUser(realm, "impersonated-user");
impersonatedUser.setEnabled(true);
session.userCredentialManager().updateCredential(realm, impersonatedUser, UserCredentialModel.password("password"));
impersonatedUser.grantRole(exampleRole);
}
private static void removeDirectExchanger(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName(TEST);
realm.removeClient(realm.getClientByClientId("direct-exchanger").getId());
realm.removeClient(realm.getClientByClientId("target").getId());
realm.removeRole(realm.getRole("example"));
session.users().removeUser(realm, session.users().getUserByUsername("impersonated-user", realm));
}
private Response checkTokenExchange() {
Client httpClient = ClientBuilder.newClient();
WebTarget exchangeUrl = httpClient.target(OAuthClient.AUTH_SERVER_ROOT)
.path("/realms")
.path(TEST)
.path("protocol/openid-connect/token");
Response response = exchangeUrl.request()
.header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader("direct-exchanger", "secret"))
.post(Entity.form(
new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.REQUESTED_SUBJECT, "impersonated-user")
));
return response;
}
}