Add tests for offline access, introspection and userinfo endpoint
This commit is contained in:
parent
d70735f64d
commit
d59ceb17e9
3 changed files with 241 additions and 339 deletions
|
@ -34,6 +34,7 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
|||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
@ -123,6 +124,18 @@ public class Creator<T> implements AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
public static Creator<IdentityProviderResource> create(RealmResource realmResource, String identityProviderAlias, IdentityProviderMapperRepresentation rep) {
|
||||
final IdentityProvidersResource res = realmResource.identityProviders();
|
||||
assertThat("Identity provider alias must be specified", identityProviderAlias, Matchers.notNullValue());
|
||||
rep.setIdentityProviderAlias(identityProviderAlias);
|
||||
try (Response response = res.get(identityProviderAlias).addMapper(rep)) {
|
||||
String createdId = getCreatedId(response);
|
||||
final IdentityProviderResource r = res.get(identityProviderAlias);
|
||||
LOG.debugf("Created identity provider mapper ID %s", createdId);
|
||||
return new Creator(createdId, r, () -> r.delete(createdId));
|
||||
}
|
||||
}
|
||||
|
||||
private final String id;
|
||||
private final T resource;
|
||||
private final Runnable closer;
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.IdentityProviderModel;
|
|||
import org.keycloak.models.IdentityProviderSyncMode;
|
||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
|
||||
|
@ -37,6 +38,8 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
|
|||
public static final String USER_INFO_CLAIM = "user-claim";
|
||||
public static final String HARDOCDED_CLAIM = "test";
|
||||
public static final String HARDOCDED_VALUE = "value";
|
||||
public static final String CONSUMER_BROKER_APP_CLIENT_ID = "broker-app";
|
||||
public static final String CONSUMER_BROKER_APP_SECRET = "broker-app-secret";
|
||||
|
||||
@Override
|
||||
public RealmRepresentation createProviderRealm() {
|
||||
|
@ -166,9 +169,9 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
|
|||
@Override
|
||||
public List<ClientRepresentation> createConsumerClients() {
|
||||
ClientRepresentation client = new ClientRepresentation();
|
||||
client.setClientId("broker-app");
|
||||
client.setClientId(CONSUMER_BROKER_APP_CLIENT_ID);
|
||||
client.setName("broker-app");
|
||||
client.setSecret("broker-app-secret");
|
||||
client.setSecret(CONSUMER_BROKER_APP_SECRET);
|
||||
client.setEnabled(true);
|
||||
client.setDirectAccessGrantsEnabled(true);
|
||||
|
||||
|
@ -179,6 +182,7 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration {
|
|||
"/auth/realms/" + REALM_CONS_NAME + "/app");
|
||||
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setPostLogoutRedirectUris(Collections.singletonList("+"));
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUseRefreshTokenForClientCredentialsGrant(true);
|
||||
|
||||
return Collections.singletonList(client);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.testsuite.broker;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
|
@ -13,24 +14,33 @@ import org.keycloak.admin.client.resource.ClientsResource;
|
|||
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
|
||||
import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper;
|
||||
import org.keycloak.broker.oidc.mappers.UserAttributeMapper;
|
||||
import org.keycloak.broker.provider.ConfigConstants;
|
||||
import org.keycloak.broker.provider.HardcodedRoleMapper;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.IdentityProviderSyncMode;
|
||||
import org.keycloak.models.light.LightweightUserAdapter;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
|
@ -39,35 +49,45 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
|||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.updaters.Creator;
|
||||
import org.keycloak.testsuite.util.AccountHelper;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
|
||||
import org.keycloak.util.TokenUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Rule;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.hasItems;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
import static org.keycloak.models.utils.TimeBasedOTP.DEFAULT_INTERVAL_SECONDS;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.removeUserByUsername;
|
||||
import static org.keycloak.testsuite.broker.BrokerRunOnServerUtil.configurePostBrokerLoginWithOTP;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_CONS_NAME;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_PROV_NAME;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||
import static org.keycloak.testsuite.util.ProtocolMapperUtil.createHardcodedClaim;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot;
|
||||
import static org.keycloak.testsuite.broker.KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_CLIENT_ID;
|
||||
import static org.keycloak.testsuite.broker.KcOidcBrokerConfiguration.CONSUMER_BROKER_APP_SECRET;
|
||||
|
||||
/**
|
||||
* Final class as it's not intended to be overriden. Feel free to remove "final" if you really know what you are doing.
|
||||
|
@ -273,172 +293,6 @@ public final class KcOidcBrokerTransientSessionsTest extends AbstractAdvancedBro
|
|||
Assert.assertEquals("hard-coded", user.getAttributes().get("hard-coded").get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Refers to in old test suite: PostBrokerFlowTest#testBrokerReauthentication_samlBrokerWithOTPRequired
|
||||
*/
|
||||
@Test
|
||||
public void testReauthenticationSamlBrokerWithOTPRequired() throws Exception {
|
||||
assumeFalse("OTP does not apply to transient sessions (there is no second login)", isUsingTransientSessions());
|
||||
|
||||
KcSamlBrokerConfiguration samlBrokerConfig = KcSamlBrokerConfiguration.INSTANCE;
|
||||
ClientRepresentation samlClient = samlBrokerConfig.createProviderClients().get(0);
|
||||
IdentityProviderRepresentation samlBroker = samlBrokerConfig.setUpIdentityProvider();
|
||||
RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
|
||||
|
||||
try {
|
||||
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
|
||||
adminClient.realm(bc.providerRealmName()).clients().create(samlClient);
|
||||
consumerRealm.identityProviders().create(samlBroker);
|
||||
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
|
||||
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(samlBrokerConfig.getIDPAlias()));
|
||||
logInWithBroker(samlBrokerConfig);
|
||||
|
||||
totpPage.assertCurrent();
|
||||
String totpSecret = totpPage.getTotpSecret();
|
||||
totpPage.configure(totp.generateTOTP(totpSecret));
|
||||
|
||||
logoutFromConsumerRealm();
|
||||
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
|
||||
|
||||
setOtpTimeOffset(DEFAULT_INTERVAL_SECONDS, totp);
|
||||
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "account already exists", false);
|
||||
idpConfirmLinkPage.assertCurrent();
|
||||
idpConfirmLinkPage.clickLinkAccount();
|
||||
|
||||
loginPage.clickSocial(samlBrokerConfig.getIDPAlias());
|
||||
waitForPage(driver, "sign in to", true);
|
||||
log.debug("Logging in");
|
||||
loginTotpPage.login(totp.generateTOTP(totpSecret));
|
||||
|
||||
assertNumFederatedIdentities(consumerRealm.users().search(samlBrokerConfig.getUserLogin()).get(0).getId(), 2);
|
||||
} finally {
|
||||
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
|
||||
removeUserByUsername(consumerRealm, "consumer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refers to in old test suite: PostBrokerFlowTest#testBrokerReauthentication_oidcBrokerWithOTPRequired
|
||||
*/
|
||||
@Test
|
||||
public void testReauthenticationOIDCBrokerWithOTPRequired() throws Exception {
|
||||
assumeFalse("Account linking does not apply to transient sessions", isUsingTransientSessions());
|
||||
|
||||
KcSamlBrokerConfiguration samlBrokerConfig = KcSamlBrokerConfiguration.INSTANCE;
|
||||
ClientRepresentation samlClient = samlBrokerConfig.createProviderClients().get(0);
|
||||
IdentityProviderRepresentation samlBroker = samlBrokerConfig.setUpIdentityProvider();
|
||||
RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
|
||||
|
||||
try {
|
||||
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
|
||||
adminClient.realm(bc.providerRealmName()).clients().create(samlClient);
|
||||
consumerRealm.identityProviders().create(samlBroker);
|
||||
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
|
||||
logInWithBroker(samlBrokerConfig);
|
||||
logoutFromConsumerRealm();
|
||||
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
|
||||
|
||||
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(bc.getIDPAlias()));
|
||||
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "account already exists", false);
|
||||
idpConfirmLinkPage.assertCurrent();
|
||||
idpConfirmLinkPage.clickLinkAccount();
|
||||
loginPage.clickSocial(samlBrokerConfig.getIDPAlias());
|
||||
|
||||
totpPage.assertCurrent();
|
||||
String totpSecret = totpPage.getTotpSecret();
|
||||
totpPage.configure(totp.generateTOTP(totpSecret));
|
||||
logoutFromConsumerRealm();
|
||||
|
||||
assertNumFederatedIdentities(consumerRealm.users().search(samlBrokerConfig.getUserLogin()).get(0).getId(), 2);
|
||||
} finally {
|
||||
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
|
||||
removeUserByUsername(consumerRealm, "consumer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refers to in old test suite: PostBrokerFlowTest#testBrokerReauthentication_bothBrokerWithOTPRequired
|
||||
*/
|
||||
@Test
|
||||
public void testReauthenticationBothBrokersWithOTPRequired() throws Exception {
|
||||
assumeFalse("Account linking does not apply to transient sessions", isUsingTransientSessions());
|
||||
|
||||
final RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
|
||||
final RealmResource providerRealm = adminClient.realm(bc.providerRealmName());
|
||||
|
||||
try (RealmAttributeUpdater rauConsumer = new RealmAttributeUpdater(consumerRealm).setOtpPolicyCodeReusable(true).update();
|
||||
RealmAttributeUpdater rauProvider = new RealmAttributeUpdater(providerRealm).setOtpPolicyCodeReusable(true).update()) {
|
||||
|
||||
KcSamlBrokerConfiguration samlBrokerConfig = KcSamlBrokerConfiguration.INSTANCE;
|
||||
ClientRepresentation samlClient = samlBrokerConfig.createProviderClients().get(0);
|
||||
IdentityProviderRepresentation samlBroker = samlBrokerConfig.setUpIdentityProvider();
|
||||
|
||||
try {
|
||||
updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin);
|
||||
providerRealm.clients().create(samlClient);
|
||||
consumerRealm.identityProviders().create(samlBroker);
|
||||
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
|
||||
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(samlBrokerConfig.getIDPAlias()));
|
||||
logInWithBroker(samlBrokerConfig);
|
||||
totpPage.assertCurrent();
|
||||
String totpSecret = totpPage.getTotpSecret();
|
||||
totpPage.configure(totp.generateTOTP(totpSecret));
|
||||
logoutFromConsumerRealm();
|
||||
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
|
||||
|
||||
testingClient.server(bc.consumerRealmName()).run(configurePostBrokerLoginWithOTP(bc.getIDPAlias()));
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
|
||||
logInWithBroker(bc);
|
||||
|
||||
waitForPage(driver, "account already exists", false);
|
||||
idpConfirmLinkPage.assertCurrent();
|
||||
idpConfirmLinkPage.clickLinkAccount();
|
||||
loginPage.clickSocial(samlBrokerConfig.getIDPAlias());
|
||||
|
||||
loginTotpPage.assertCurrent();
|
||||
loginTotpPage.login(totp.generateTOTP(totpSecret));
|
||||
logoutFromConsumerRealm();
|
||||
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
|
||||
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
|
||||
logInWithBroker(bc);
|
||||
|
||||
loginTotpPage.assertCurrent();
|
||||
loginTotpPage.login(totp.generateTOTP(totpSecret));
|
||||
|
||||
assertNumFederatedIdentities(consumerRealm.users().search(samlBrokerConfig.getUserLogin()).get(0).getId(), 2);
|
||||
} finally {
|
||||
updateExecutions(AbstractBrokerTest::setUpMissingUpdateProfileOnFirstLogin);
|
||||
removeUserByUsername(consumerRealm, "consumer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidIssuedFor() {
|
||||
loginUser();
|
||||
|
@ -512,26 +366,6 @@ public final class KcOidcBrokerTransientSessionsTest extends AbstractAdvancedBro
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdPForceSyncUserAttributes() {
|
||||
checkUpdatedUserAttributesIdP(true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdPForceSyncTrustEmailUserAttributes() {
|
||||
checkUpdatedUserAttributesIdP(true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdPNotForceSyncUserAttributes() {
|
||||
checkUpdatedUserAttributesIdP(false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdPNotForceSyncTrustEmailUserAttributes() {
|
||||
checkUpdatedUserAttributesIdP(false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWithClaimFilter() {
|
||||
IdentityProviderResource identityProviderResource = getIdentityProviderResource();
|
||||
|
@ -613,149 +447,6 @@ public final class KcOidcBrokerTransientSessionsTest extends AbstractAdvancedBro
|
|||
assertThat("Claim value didn't change", idProvider.getConfig().get(IdentityProviderModel.CLAIM_FILTER_VALUE), Matchers.equalTo(claimFilterValue));
|
||||
}
|
||||
|
||||
private void checkUpdatedUserAttributesIdP(boolean isForceSync, boolean isTrustEmail) {
|
||||
assumeFalse("Updating user attributes is not supported when using transient sessions", isUsingTransientSessions());
|
||||
|
||||
final String IDP_NAME = getBrokerConfiguration().getIDPAlias();
|
||||
final String USERNAME = "demo-user";
|
||||
final String PASSWORD = "demo-pwd";
|
||||
final String NEW_USERNAME = "demo-user-new";
|
||||
|
||||
final String FIRST_NAME = "John";
|
||||
final String LAST_NAME = "Doe";
|
||||
final String EMAIL = "mail@example.com";
|
||||
|
||||
final String NEW_FIRST_NAME = "Jack";
|
||||
final String NEW_LAST_NAME = "Doee";
|
||||
final String NEW_EMAIL = "mail123@example.com";
|
||||
|
||||
RealmResource providerRealmResource = realmsResouce().realm(bc.providerRealmName());
|
||||
allowUserEdit(providerRealmResource);
|
||||
|
||||
UsersResource providerUsersResource = providerRealmResource.users();
|
||||
|
||||
String providerUserID = createUser(bc.providerRealmName(), USERNAME, PASSWORD, FIRST_NAME, LAST_NAME, EMAIL,
|
||||
user -> user.setEmailVerified(true));
|
||||
UserResource providerUserResource = providerUsersResource.get(providerUserID);
|
||||
|
||||
try {
|
||||
IdentityProviderResource consumerIdentityResource = getIdentityProviderResource();
|
||||
IdentityProviderRepresentation idProvider = consumerIdentityResource.toRepresentation();
|
||||
|
||||
updateIdPSyncMode(idProvider, consumerIdentityResource,
|
||||
isForceSync ? IdentityProviderSyncMode.FORCE : IdentityProviderSyncMode.IMPORT, isTrustEmail);
|
||||
|
||||
// login to create the user in the consumer realm
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
|
||||
WaitUtils.waitForPageToLoad();
|
||||
|
||||
assertThat(driver.getTitle(), Matchers.containsString("Sign in to " + bc.consumerRealmName()));
|
||||
logInWithIdp(IDP_NAME, USERNAME, PASSWORD);
|
||||
|
||||
UserRepresentation userRepresentation = AccountHelper.getUserRepresentation(adminClient.realm(bc.providerRealmName()), USERNAME);
|
||||
|
||||
assertThat(userRepresentation.getUsername(), Matchers.equalTo(USERNAME));
|
||||
assertThat(userRepresentation.getEmail(), Matchers.equalTo(EMAIL));
|
||||
assertThat(userRepresentation.getFirstName(), Matchers.equalTo(FIRST_NAME));
|
||||
assertThat(userRepresentation.getLastName(), Matchers.equalTo(LAST_NAME));
|
||||
|
||||
RealmResource consumerRealmResource = realmsResouce().realm(bc.consumerRealmName());
|
||||
UserRepresentation consumerUser = getConsumerUserRepresentation(USERNAME);
|
||||
assertThat(consumerUser, Matchers.notNullValue());
|
||||
String consumerUserID = consumerUser.getId();
|
||||
UserResource consumerUserResource = consumerRealmResource.users().get(consumerUserID);
|
||||
|
||||
checkFederatedIdentityLink(consumerUserResource, providerUserID, USERNAME);
|
||||
assertThat(consumerUserResource.toRepresentation().isEmailVerified(), Matchers.equalTo(isTrustEmail));
|
||||
|
||||
logoutFromConsumerRealm();
|
||||
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), USERNAME);
|
||||
|
||||
// set email verified to true on the consumer resource
|
||||
consumerUser = consumerUserResource.toRepresentation();
|
||||
consumerUser.setEmailVerified(true);
|
||||
consumerUserResource.update(consumerUser);
|
||||
consumerUserResource = consumerRealmResource.users().get(consumerUserID);
|
||||
assertThat(consumerUserResource.toRepresentation().isEmailVerified(), Matchers.is(true));
|
||||
|
||||
// modify provider user with the new values
|
||||
UserRepresentation providerUser = providerUserResource.toRepresentation();
|
||||
providerUser.setUsername(NEW_USERNAME);
|
||||
providerUser.setFirstName(NEW_FIRST_NAME);
|
||||
providerUser.setLastName(NEW_LAST_NAME);
|
||||
providerUser.setEmail(NEW_EMAIL);
|
||||
providerUser.setEmailVerified(true);
|
||||
providerUserResource.update(providerUser);
|
||||
|
||||
// login again to force sync if force mode
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
|
||||
WaitUtils.waitForPageToLoad();
|
||||
|
||||
assertThat(driver.getTitle(), Matchers.containsString("Sign in to " + bc.consumerRealmName()));
|
||||
logInWithIdp(IDP_NAME, NEW_USERNAME, PASSWORD);
|
||||
|
||||
userRepresentation = getConsumerUserRepresentation(USERNAME);
|
||||
|
||||
// consumer username stays the same, even when sync mode is force
|
||||
assertThat(userRepresentation.getUsername(), Matchers.equalTo(USERNAME));
|
||||
// other consumer attributes are updated, when sync mode is force
|
||||
assertThat(userRepresentation.getEmail(), Matchers.equalTo(isForceSync ? NEW_EMAIL : EMAIL));
|
||||
assertThat(userRepresentation.getFirstName(), Matchers.equalTo(isForceSync ? NEW_FIRST_NAME : FIRST_NAME));
|
||||
assertThat(userRepresentation.getLastName(), Matchers.equalTo(isForceSync ? NEW_LAST_NAME : LAST_NAME));
|
||||
|
||||
consumerUserResource = consumerRealmResource.users().get(consumerUserID);
|
||||
checkFederatedIdentityLink(consumerUserResource, providerUserID, isForceSync ? NEW_USERNAME : USERNAME);
|
||||
// the email verified should be reverted to false if force-sync and not trust-email
|
||||
assertThat(consumerUserResource.toRepresentation().isEmailVerified(), Matchers.equalTo(!isForceSync || isTrustEmail));
|
||||
} finally {
|
||||
providerUsersResource.delete(providerUserID);
|
||||
}
|
||||
}
|
||||
|
||||
private void allowUserEdit(RealmResource realmResource) {
|
||||
RealmRepresentation realm = realmResource.toRepresentation();
|
||||
realm.setEditUsernameAllowed(true);
|
||||
realmResource.update(realm);
|
||||
}
|
||||
|
||||
private void checkFederatedIdentityLink(UserResource userResource, String userID, String username) {
|
||||
if (isUsingTransientSessions()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<FederatedIdentityRepresentation> federatedIdentities = userResource.getFederatedIdentity();
|
||||
assertThat(federatedIdentities, Matchers.hasSize(1));
|
||||
FederatedIdentityRepresentation federatedIdentity = federatedIdentities.get(0);
|
||||
assertThat(federatedIdentity.getIdentityProvider(), Matchers.equalTo(IDP_OIDC_ALIAS));
|
||||
assertThat(federatedIdentity.getUserId(), Matchers.equalTo(userID));
|
||||
assertThat(federatedIdentity.getUserName(), Matchers.equalTo(username));
|
||||
}
|
||||
|
||||
private void updateIdPSyncMode(IdentityProviderRepresentation idProvider, IdentityProviderResource idProviderResource,
|
||||
IdentityProviderSyncMode syncMode, boolean trustEmail) {
|
||||
assertThat(idProvider, Matchers.notNullValue());
|
||||
assertThat(idProviderResource, Matchers.notNullValue());
|
||||
assertThat(syncMode, Matchers.notNullValue());
|
||||
|
||||
if (idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE).equals(syncMode.name())
|
||||
&& idProvider.isTrustEmail() == trustEmail) {
|
||||
return;
|
||||
}
|
||||
|
||||
idProvider.getConfig().put(IdentityProviderModel.SYNC_MODE, syncMode.name());
|
||||
idProvider.setTrustEmail(trustEmail);
|
||||
idProviderResource.update(idProvider);
|
||||
|
||||
idProvider = idProviderResource.toRepresentation();
|
||||
assertThat("Cannot get Identity Provider", idProvider, Matchers.notNullValue());
|
||||
assertThat("Sync mode didn't change", idProvider.getConfig().get(IdentityProviderModel.SYNC_MODE), Matchers.equalTo(syncMode.name()));
|
||||
assertThat("TrustEmail didn't change", idProvider.isTrustEmail(), Matchers.equalTo(trustEmail));
|
||||
}
|
||||
|
||||
private UserRepresentation getFederatedIdentity() {
|
||||
return getConsumerUserRepresentation(bc.getUserLogin());
|
||||
}
|
||||
|
@ -764,9 +455,179 @@ public final class KcOidcBrokerTransientSessionsTest extends AbstractAdvancedBro
|
|||
return realmsResouce().realm(bc.consumerRealmName()).identityProviders().get(bc.getIDPAlias());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleSignOn() {
|
||||
loginWithBrokerUsingOAuthClient(CustomKcOidcBrokerConfiguration.CONSUMER_ADDITIONAL_BROKER_APP_CLIENT_ID);
|
||||
|
||||
oauth.clientId(CONSUMER_BROKER_APP_CLIENT_ID);
|
||||
oauth.openLoginForm();
|
||||
|
||||
Assert.assertTrue("Should be logged in", driver.getTitle().endsWith("AUTH_RESPONSE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserInfoEndpoint() throws Exception {
|
||||
EventRepresentation loginEvent = loginWithBrokerUsingOAuthClient(CONSUMER_BROKER_APP_CLIENT_ID);
|
||||
String lwUserId = loginEvent.getUserId();
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, CONSUMER_BROKER_APP_SECRET);
|
||||
|
||||
// Check that userInfo can be invoked
|
||||
var userInfoResponse = oauth.doUserInfoRequestByGet(tokenResponse.getAccessToken());
|
||||
assertThat(userInfoResponse.getUserInfo().getSub(), is(lwUserId));
|
||||
assertThat(userInfoResponse.getUserInfo().getPreferredUsername(), is(bc.getUserLogin()));
|
||||
assertThat(userInfoResponse.getUserInfo().getEmail(), is(bc.getUserEmail()));
|
||||
|
||||
// Check that tokenIntrospection can be invoked
|
||||
var introspectionResponse = oauth.introspectAccessTokenWithClientCredential(CONSUMER_BROKER_APP_CLIENT_ID, CONSUMER_BROKER_APP_SECRET, tokenResponse.getAccessToken());
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonNode jsonNode = objectMapper.readTree(introspectionResponse);
|
||||
org.junit.Assert.assertEquals(true, jsonNode.get("active").asBoolean());
|
||||
org.junit.Assert.assertEquals(bc.getUserEmail(), jsonNode.get("email").asText());
|
||||
}
|
||||
|
||||
private EventRepresentation loginWithBrokerUsingOAuthClient(String consumerClientId) {
|
||||
oauth.clientId(consumerClientId);
|
||||
oauth.realm(bc.consumerRealmName());
|
||||
oauth.doLoginSocial(bc.getIDPAlias(), bc.getUserLogin(), bc.getUserPassword());
|
||||
events.clear();
|
||||
oauth.updateAccountInformation(bc.getUserLogin(), bc.getUserEmail());
|
||||
|
||||
EventRepresentation loginEvent;
|
||||
do {
|
||||
loginEvent = events.poll();
|
||||
} while (loginEvent != null && ! Objects.equals(EventType.LOGIN.name(), loginEvent.getType()));
|
||||
|
||||
assertThat(loginEvent, notNullValue());
|
||||
assertThat(loginEvent.getClientId(), is(consumerClientId));
|
||||
assertThat(loginEvent.getUserId(), Matchers.containsString(LightweightUserAdapter.ID_PREFIX));
|
||||
|
||||
return loginEvent;
|
||||
}
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Test // Based on OfflineTokenTest.offlineTokenBrowserFlow()
|
||||
public void offlineTokenBrowserFlow() throws Exception {
|
||||
final RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());
|
||||
RealmRepresentation consumerRealmRep = consumerRealm.toRepresentation();
|
||||
|
||||
// Create mapper which assigns offline_access role to users from "provider" IdP
|
||||
try (var c = Creator.create(consumerRealm, bc.getIDPAlias(), createHardcodedOfflineRoleMapper())) {
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
EventRepresentation loginEvent = loginWithBrokerUsingOAuthClient(CONSUMER_BROKER_APP_CLIENT_ID);
|
||||
String lwUserId = loginEvent.getUserId();
|
||||
|
||||
final String sessionId = loginEvent.getSessionId();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, CONSUMER_BROKER_APP_SECRET);
|
||||
|
||||
AccessToken token = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
String offlineTokenString = tokenResponse.getRefreshToken();
|
||||
RefreshToken offlineToken = oauth.parseRefreshToken(offlineTokenString);
|
||||
|
||||
events.expectCodeToToken(codeId, sessionId)
|
||||
.realm(consumerRealmRep)
|
||||
.client(CONSUMER_BROKER_APP_CLIENT_ID)
|
||||
.user(lwUserId)
|
||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||
.assertEvent();
|
||||
|
||||
assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineToken.getType());
|
||||
assertEquals(0, offlineToken.getExpiration());
|
||||
|
||||
assertTrue(tokenResponse.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
|
||||
|
||||
String newRefreshTokenString = testRefreshWithOfflineToken(token, offlineToken, offlineTokenString, sessionId, consumerRealmRep, lwUserId);
|
||||
|
||||
// Change offset to very big value to ensure offline session expires
|
||||
setTimeOffset(3000000);
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(newRefreshTokenString, CONSUMER_BROKER_APP_SECRET);
|
||||
RefreshToken newRefreshToken = oauth.parseRefreshToken(newRefreshTokenString);
|
||||
org.junit.Assert.assertEquals(400, response.getStatusCode());
|
||||
assertEquals("invalid_grant", response.getError());
|
||||
|
||||
events.expectRefresh(offlineToken.getId(), newRefreshToken.getSessionState())
|
||||
.realm(consumerRealmRep)
|
||||
.client(CONSUMER_BROKER_APP_CLIENT_ID)
|
||||
.error(Errors.INVALID_TOKEN)
|
||||
.user(lwUserId)
|
||||
.clearDetails()
|
||||
.assertEvent();
|
||||
} finally {
|
||||
setTimeOffset(0);
|
||||
}
|
||||
}
|
||||
|
||||
private String testRefreshWithOfflineToken(AccessToken oldToken, RefreshToken offlineToken, String offlineTokenString,
|
||||
final String sessionId, RealmRepresentation consumerRealmRep, String userId) {
|
||||
// Change offset to big value to ensure userSession expired
|
||||
setTimeOffset(99999);
|
||||
assertFalse(oldToken.isActive());
|
||||
assertTrue(offlineToken.isActive());
|
||||
|
||||
// Assert userSession expired
|
||||
testingClient.testing().removeExpired(bc.consumerRealmName());
|
||||
try {
|
||||
testingClient.testing().removeUserSession(bc.consumerRealmName(), sessionId);
|
||||
} catch (NotFoundException nfe) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(offlineTokenString, CONSUMER_BROKER_APP_SECRET);
|
||||
AccessToken refreshedToken = oauth.verifyToken(response.getAccessToken());
|
||||
org.junit.Assert.assertEquals(200, response.getStatusCode());
|
||||
|
||||
// Assert new refreshToken in the response
|
||||
String newRefreshToken = response.getRefreshToken();
|
||||
org.junit.Assert.assertNotNull(newRefreshToken);
|
||||
org.junit.Assert.assertNotEquals(oldToken.getId(), refreshedToken.getId());
|
||||
|
||||
// Assert scope parameter contains "offline_access"
|
||||
assertTrue(response.getScope().contains(OAuth2Constants.OFFLINE_ACCESS));
|
||||
|
||||
org.junit.Assert.assertEquals(userId, refreshedToken.getSubject());
|
||||
|
||||
assertTrue(refreshedToken.getRealmAccess().isUserInRole(Constants.OFFLINE_ACCESS_ROLE));
|
||||
|
||||
EventRepresentation refreshEvent = events.expectRefresh(offlineToken.getId(), sessionId)
|
||||
.realm(consumerRealmRep)
|
||||
.client(CONSUMER_BROKER_APP_CLIENT_ID)
|
||||
.user(userId)
|
||||
.removeDetail(Details.UPDATED_REFRESH_TOKEN_ID)
|
||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||
.assertEvent();
|
||||
org.junit.Assert.assertNotEquals(oldToken.getId(), refreshEvent.getDetails().get(Details.TOKEN_ID));
|
||||
|
||||
setTimeOffset(0);
|
||||
return newRefreshToken;
|
||||
}
|
||||
|
||||
private IdentityProviderMapperRepresentation createHardcodedOfflineRoleMapper() {
|
||||
var res = new IdentityProviderMapperRepresentation();
|
||||
res.setName("hardcoded-role-mapper");
|
||||
res.setIdentityProviderMapper(HardcodedRoleMapper.PROVIDER_ID);
|
||||
res.setConfig(ImmutableMap.<String, String> builder()
|
||||
.put(ConfigConstants.ROLE, OAuth2Constants.OFFLINE_ACCESS)
|
||||
.build());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
private static final CustomKcOidcBrokerConfiguration BROKER_CONFIG_INSTANCE = new CustomKcOidcBrokerConfiguration();
|
||||
|
||||
static class CustomKcOidcBrokerConfiguration extends KcOidcBrokerConfiguration {
|
||||
|
||||
public static final String CONSUMER_ADDITIONAL_BROKER_APP_CLIENT_ID = "additional-broker-app";
|
||||
public static final String CONSUMER_ADDITIONAL_BROKER_APP_SECRET = "broker-app-secret";
|
||||
|
||||
@Override
|
||||
public List<ClientRepresentation> createProviderClients() {
|
||||
List<ClientRepresentation> clients = super.createProviderClients();
|
||||
|
@ -793,6 +654,30 @@ public final class KcOidcBrokerTransientSessionsTest extends AbstractAdvancedBro
|
|||
return clients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientRepresentation> createConsumerClients() {
|
||||
List<ClientRepresentation> res = new LinkedList<>(super.createConsumerClients());
|
||||
|
||||
ClientRepresentation client = new ClientRepresentation();
|
||||
client.setClientId(CONSUMER_ADDITIONAL_BROKER_APP_CLIENT_ID);
|
||||
client.setName("additional-broker-app");
|
||||
client.setSecret(CONSUMER_ADDITIONAL_BROKER_APP_SECRET);
|
||||
client.setEnabled(true);
|
||||
client.setDirectAccessGrantsEnabled(true);
|
||||
|
||||
client.setRedirectUris(Collections.singletonList(getConsumerRoot() +
|
||||
"/auth/*"));
|
||||
|
||||
client.setBaseUrl(getConsumerRoot() +
|
||||
"/auth/realms/" + REALM_CONS_NAME + "/app2");
|
||||
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setPostLogoutRedirectUris(Collections.singletonList("+"));
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUseRefreshTokenForClientCredentialsGrant(true);
|
||||
|
||||
res.add(client);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyDefaultConfiguration(Map<String, String> config, IdentityProviderSyncMode syncMode) {
|
||||
super.applyDefaultConfiguration(config, syncMode);
|
||||
|
|
Loading…
Reference in a new issue