KEYCLOAK-10029 Offline token migration fix. Always test offline-token migration when run MigrationTest

This commit is contained in:
mposolda 2020-02-18 18:09:40 +01:00 committed by Marek Posolda
parent db26520046
commit bc1146ac2f
10 changed files with 178 additions and 34 deletions

View file

@ -179,6 +179,7 @@ public class TokenManager {
if (oldTokenScope == null && userSession.isOffline()) {
logger.debugf("Migrating offline token of user '%s' for client '%s' of realm '%s'", user.getUsername(), client.getClientId(), realm.getName());
MigrationUtils.migrateOldOfflineToken(session, realm, client, user);
oldTokenScope = OAuth2Constants.OFFLINE_ACCESS;
}
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, oldTokenScope, session);

View file

@ -44,7 +44,7 @@ and adapter are all in the same JVM and you can debug them easily. If it is not
Or slightly longer version (that allows you to specify debugging port as well as wait till you attach the debugger):
-Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5006 -Xnoagent -Djava.compiler=NONE"
-Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006 -Xnoagent -Djava.compiler=NONE"
and you will be able to attach remote debugger to the test. Unfortunately server and adapter are running in different JVMs, so this won't help to debug those.

View file

@ -43,7 +43,7 @@ public class MigrationContext {
try (FileInputStream fis = new FileInputStream(file)) {
String offlineToken = StreamUtil.readString(fis, Charset.forName("UTF-8"));
logger.infof("Successfully read offline token: %s", offlineToken);
File f = new File(file);
f.delete();
logger.infof("Deleted file with offline token: %s", file);
@ -67,7 +67,7 @@ public class MigrationContext {
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
oauth.realm("Migration");
oauth.clientId("migration-test-client");
OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("b2c07929-69e3-44c6-8d7f-76939000b3e4", "migration-test-user", "admin");
OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("secret", "offline-test-user", "password2");
return tokenResponse.getRefreshToken();
} catch (Exception e) {
throw new RuntimeException(e);
@ -77,7 +77,7 @@ public class MigrationContext {
private void saveOfflineToken(String offlineToken) throws Exception {
String file = getOfflineTokenLocation();
logger.infof("Saving offline token to file: %s", file);
logger.infof("Saving offline token to file: %s, Offline token is: %s", file, offlineToken);
try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
writer.print(offlineToken);
@ -85,10 +85,12 @@ public class MigrationContext {
}
// Needs to save offline token inside "basedir". There are issues with saving into directory "target" as it's cleared among restarts and
// using "mvn install" instead of "mvn clean install" doesn't work ATM. Improve if needed...
private String getOfflineTokenLocation() {
return System.getProperty("basedir") + "/offline-token.txt";
String tmpDir = System.getProperty("java.io.tmpdir", "");
if (tmpDir == null) {
tmpDir = System.getProperty("basedir");
}
return tmpDir + "/offline-token.txt";
}
}

View file

@ -18,6 +18,7 @@ package org.keycloak.testsuite.migration;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.RealmResource;
@ -44,6 +45,7 @@ import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
@ -62,9 +64,12 @@ import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.migration.MigrationContext;
import org.keycloak.testsuite.exportimport.ExportImportUtil;
import org.keycloak.testsuite.runonserver.RunHelpers;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.WaitUtils;
import org.keycloak.util.TokenUtil;
import java.io.IOException;
import java.net.URI;
@ -126,7 +131,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
assertNames(migrationRealm.clients().findAll(), expectedClientIds.toArray(new String[expectedClientIds.size()]));
String id2 = migrationRealm.clients().findByClientId("migration-test-client").get(0).getId();
assertNames(migrationRealm.clients().get(id2).roles().list(), "migration-test-client-role");
assertNames(migrationRealm.users().search("", 0, 5), "migration-test-user");
assertNames(migrationRealm.users().search("", 0, 5), "migration-test-user", "offline-test-user");
assertNames(migrationRealm.groups().groups(), "migration-test-group");
}
@ -180,10 +185,6 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testDuplicateEmailSupport(masterRealm, migrationRealm);
}
protected void testMigrationTo2_5_1() throws Exception {
testOfflineTokenLogin();
}
/**
* @see org.keycloak.migration.migrators.MigrateTo3_0_0
*/
@ -649,9 +650,32 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
oauth.realm(MIGRATION);
oauth.clientId("migration-test-client");
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(oldOfflineToken, "b2c07929-69e3-44c6-8d7f-76939000b3e4");
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(oldOfflineToken, "secret");
if (response.getError() != null) {
String errorMessage = String.format("Error when refreshing offline token. Error: %s, Error details: %s, offline token from previous version: %s",
response.getError(), response.getErrorDescription(), oldOfflineToken);
log.error(errorMessage);
Assert.fail(errorMessage);
}
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
assertEquals("migration-test-user", accessToken.getPreferredUsername());
assertEquals("offline-test-user", accessToken.getPreferredUsername());
// KEYCLOAK-10029 - Doublecheck that refresh token in the response is also offline token. Doublecheck that it can be used to another successful refresh
String newOfflineToken1 = response.getRefreshToken();
assertOfflineToken(newOfflineToken1);
response = oauth.doRefreshTokenRequest(newOfflineToken1, "secret");
String newOfflineToken2 = response.getRefreshToken();
assertOfflineToken(newOfflineToken2);
}
private void assertOfflineToken(String offlineToken) {
RefreshToken offlineTokenParsed = oauth.parseRefreshToken(offlineToken);
assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineTokenParsed.getType());
assertEquals(0, offlineTokenParsed.getExpiration());
assertTrue(TokenUtil.hasScope(offlineTokenParsed.getScope(), OAuth2Constants.OFFLINE_ACCESS));
}
private void testRealmDefaultClientScopes(RealmResource realm) {
@ -748,19 +772,19 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
String otp = otpGenerator.generateTOTP("dSdmuHLQhkm54oIm0A0S");
// Try invalid password first
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("b2c07929-69e3-44c6-8d7f-76939000b3e4",
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret",
"migration-test-user", "password", otp);
Assert.assertNull(response.getAccessToken());
Assert.assertNotNull(response.getError());
// Try invalid OTP then
response = oauth.doGrantAccessTokenRequest("b2c07929-69e3-44c6-8d7f-76939000b3e4",
response = oauth.doGrantAccessTokenRequest("secret",
"migration-test-user", "password2", "invalid");
Assert.assertNull(response.getAccessToken());
Assert.assertNotNull(response.getError());
// Try successful login now
response = oauth.doGrantAccessTokenRequest("b2c07929-69e3-44c6-8d7f-76939000b3e4",
response = oauth.doGrantAccessTokenRequest("secret",
"migration-test-user", "password2", otp);
Assert.assertNull(response.getError());
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
@ -770,7 +794,6 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
}
}
protected void testOTPAuthenticatorsMigratedToConditionalFlow() {
log.info("testing optional authentication executions migrated");
@ -831,7 +854,6 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testMigrationTo2_2_0();
testMigrationTo2_3_0();
testMigrationTo2_5_0();
testMigrationTo2_5_1();
}
protected void testMigrationTo3_x() {

View file

@ -63,7 +63,6 @@ public class JsonFileImport198MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo2_2_0();
testMigrationTo2_3_0();
testMigrationTo2_5_0();
//testMigrationTo2_5_1(); // Offline tokens migration is skipped for JSON
testMigrationTo3_x();
testMigrationTo4_x(false, false);
testMigrationTo5_x();

View file

@ -59,18 +59,21 @@ public class MigrationTest extends AbstractMigrationTest {
@Test
@Migration(versionFrom = "4.")
public void migration4_xTest() {
public void migration4_xTest() throws Exception {
testMigratedData();
testMigrationTo5_x();
testMigrationTo6_x();
testMigrationTo7_x(true);
testMigrationTo8_x();
testMigrationTo9_x();
// Always test offline-token login during migration test
testOfflineTokenLogin();
}
@Test
@Migration(versionFrom = "3.")
public void migration3_xTest() {
public void migration3_xTest() throws Exception {
testMigratedData();
testMigrationTo4_x();
testMigrationTo5_x();
@ -78,11 +81,14 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo7_x(true);
testMigrationTo8_x();
testMigrationTo9_x();
// Always test offline-token login during migration test
testOfflineTokenLogin();
}
@Test
@Migration(versionFrom = "2.")
public void migration2_xTest() {
public void migration2_xTest() throws Exception {
testMigratedData();
testMigrationTo3_x();
testMigrationTo4_x();
@ -91,6 +97,9 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo7_x(true);
testMigrationTo8_x();
testMigrationTo9_x();
// Always test offline-token login during migration test
testOfflineTokenLogin();
}
@Test
@ -105,6 +114,9 @@ public class MigrationTest extends AbstractMigrationTest {
testMigrationTo7_x(false);
testMigrationTo8_x();
testMigrationTo9_x();
// Always test offline-token login during migration test
testOfflineTokenLogin();
}
}

View file

@ -1769,7 +1769,32 @@
"account" : [ "manage-account", "view-profile" ]
},
"groups" : [ "/migration-test-group" ]
} ],
},
{
"id" : "9aa0d4f7-399e-4520-92df-77403d5d2a33",
"createdTimestamp" : 1476260593350,
"username" : "offline-test-user",
"enabled" : true,
"totp" : false,
"emailVerified" : false,
"credentials" : [ {
"type" : "password",
"hashedSaltedValue" : "D3F6cEj0pNv1UvkPq2XhnH5TTg2BaR2qKQd+vMoT8Pj+cHEGvISbBujjD9+889LIhWUSbQS8nkZH0yEnrTKBAA==",
"salt" : "C2vKhAsajS53Xu816IcKIw==",
"hashIterations" : 20000,
"counter" : 0,
"algorithm" : "pbkdf2",
"digits" : 0,
"period" : 0,
"createdDate" : 1582099686822
} ],
"requiredActions" : [ ],
"realmRoles" : [ "offline_access" ],
"clientRoles" : {
"account" : [ "manage-account", "view-profile" ]
},
"groups" : [ ]
} ],
"clientScopeMappings" : {
"realm-management" : [ {
"client" : "admin-cli",
@ -2100,7 +2125,7 @@
"surrogateAuthRequired" : false,
"enabled" : true,
"clientAuthenticatorType" : "client-secret",
"secret" : "b2c07929-69e3-44c6-8d7f-76939000b3e4",
"secret" : "secret",
"redirectUris" : [ ],
"webOrigins" : [ ],
"notBefore" : 0,

View file

@ -2121,7 +2121,34 @@
"account" : [ "view-profile", "manage-account" ]
},
"groups" : [ ]
} ],
},
{
"id" : "556eb430-d574-4956-908a-83527a77932a",
"createdTimestamp" : 1489756947105,
"username" : "offline-test-user",
"enabled" : true,
"totp" : false,
"emailVerified" : false,
"credentials" : [ {
"type" : "password",
"hashedSaltedValue" : "D3F6cEj0pNv1UvkPq2XhnH5TTg2BaR2qKQd+vMoT8Pj+cHEGvISbBujjD9+889LIhWUSbQS8nkZH0yEnrTKBAA==",
"salt" : "C2vKhAsajS53Xu816IcKIw==",
"hashIterations" : 20000,
"counter" : 0,
"algorithm" : "pbkdf2",
"digits" : 0,
"period" : 0,
"createdDate" : 1582099686822,
"config" : { }
} ],
"disableableCredentialTypes" : [ "password" ],
"requiredActions" : [ ],
"realmRoles" : [ "uma_authorization", "offline_access" ],
"clientRoles" : {
"account" : [ "view-profile", "manage-account" ]
},
"groups" : [ ]
} ],
"clientScopeMappings" : {
"realm-management" : [ {
"client" : "admin-cli",
@ -2471,7 +2498,7 @@
"surrogateAuthRequired" : false,
"enabled" : true,
"clientAuthenticatorType" : "client-secret",
"secret" : "75da9358-22e0-4ab5-9609-5c74c40dd70f",
"secret" : "secret",
"redirectUris" : [ ],
"webOrigins" : [ ],
"notBefore" : 0,
@ -2481,7 +2508,7 @@
"implicitFlowEnabled" : false,
"directAccessGrantsEnabled" : true,
"serviceAccountsEnabled" : false,
"publicClient" : true,
"publicClient" : false,
"frontchannelLogout" : false,
"protocol" : "openid-connect",
"attributes" : { },

View file

@ -333,7 +333,35 @@
},
"notBefore" : 0,
"groups" : [ ]
} ],
},
{
"id" : "3a15a4f3-0e14-4b57-8753-2d774ef02fce",
"createdTimestamp" : 1531933208712,
"username" : "offline-test-user",
"enabled" : true,
"totp" : false,
"emailVerified" : false,
"credentials" : [ {
"type" : "password",
"hashedSaltedValue" : "kNwotFPNeuwelpT1HWt+E4ONXFK6wjd+h0zbzNBRGwOqacAjeY7vYN9QZQ46DlEKSdn04cEU/3RvX8WPcRegxg==",
"salt" : "rEIJDbs+BQqpx31v8mONWA==",
"hashIterations" : 27500,
"counter" : 0,
"algorithm" : "pbkdf2-sha256",
"digits" : 0,
"period" : 0,
"createdDate" : 1570002786025,
"config" : { }
} ],
"disableableCredentialTypes" : [ ],
"requiredActions" : [ ],
"realmRoles" : [ "offline_access", "uma_authorization" ],
"clientRoles" : {
"account" : [ "manage-account", "view-profile" ]
},
"notBefore" : 0,
"groups" : [ ]
} ],
"clientScopeMappings" : {
"migration-test-client": [
{
@ -679,7 +707,7 @@
"surrogateAuthRequired" : false,
"enabled" : true,
"clientAuthenticatorType" : "client-secret",
"secret" : "d926c1c0-056a-4418-86d5-f103112dec43",
"secret" : "secret",
"redirectUris" : [ ],
"webOrigins" : [ ],
"notBefore" : 0,
@ -689,7 +717,7 @@
"implicitFlowEnabled" : false,
"directAccessGrantsEnabled" : true,
"serviceAccountsEnabled" : false,
"publicClient" : true,
"publicClient" : false,
"frontchannelLogout" : false,
"protocol" : "openid-connect",
"attributes" : { },

View file

@ -338,7 +338,35 @@
},
"notBefore" : 0,
"groups" : [ ]
} ],
},
{
"id" : "189110e3-0b38-4ae3-b019-dce1f1b34512",
"createdTimestamp" : 1550760939539,
"username" : "offline-test-user",
"enabled" : true,
"totp" : false,
"emailVerified" : false,
"credentials" : [ {
"type" : "password",
"hashedSaltedValue" : "kNwotFPNeuwelpT1HWt+E4ONXFK6wjd+h0zbzNBRGwOqacAjeY7vYN9QZQ46DlEKSdn04cEU/3RvX8WPcRegxg==",
"salt" : "rEIJDbs+BQqpx31v8mONWA==",
"hashIterations" : 27500,
"counter" : 0,
"algorithm" : "pbkdf2-sha256",
"digits" : 0,
"period" : 0,
"createdDate" : 1570002786025,
"config" : { }
} ],
"disableableCredentialTypes" : [ ],
"requiredActions" : [ ],
"realmRoles" : [ "uma_authorization", "offline_access" ],
"clientRoles" : {
"account" : [ "view-profile", "manage-account" ]
},
"notBefore" : 0,
"groups" : [ ]
} ],
"scopeMappings" : [ {
"clientScope" : "offline_access",
"roles" : [ "offline_access" ]
@ -480,7 +508,7 @@
"surrogateAuthRequired" : false,
"enabled" : true,
"clientAuthenticatorType" : "client-secret",
"secret" : "ce99063e-6d4e-4342-b4ec-62a54a46e9dd",
"secret" : "secret",
"redirectUris" : [ ],
"webOrigins" : [ ],
"notBefore" : 0,
@ -490,7 +518,7 @@
"implicitFlowEnabled" : false,
"directAccessGrantsEnabled" : true,
"serviceAccountsEnabled" : false,
"publicClient" : true,
"publicClient" : false,
"frontchannelLogout" : false,
"protocol" : "openid-connect",
"attributes" : { },