mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-28 04:13:22 -04:00
fix: performing inline user import for multi-file
closes: #38251 Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
parent
a097c5a831
commit
abc448e4d1
7 changed files with 79 additions and 46 deletions
|
|
@ -191,7 +191,7 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void importRealm(RealmRepresentation rep, RealmModel newRealm, boolean skipUserDependent) {
|
||||
public void importRealm(RealmRepresentation rep, RealmModel newRealm, Runnable userImport) {
|
||||
convertDeprecatedSocialProviders(rep);
|
||||
convertDeprecatedApplications(session, rep);
|
||||
convertDeprecatedClientTemplates(rep);
|
||||
|
|
@ -481,7 +481,8 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||
}, true);
|
||||
}
|
||||
|
||||
if (!skipUserDependent) {
|
||||
if (userImport != null) {
|
||||
userImport.run();
|
||||
importRealmAuthorizationSettings(rep, newRealm, session);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,9 +40,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.storage.datastore.DefaultExportImportManager;
|
||||
|
||||
/**
|
||||
|
|
@ -148,37 +146,18 @@ public class DirImportProvider extends AbstractFileBasedImportProvider {
|
|||
if (!realmRep.getRealm().equals(realmName)) {
|
||||
throw new IllegalStateException(String.format("File name / realm name mismatch. %s, contains realm %s. File name should be %s", realmFile.getName(), realmRep.getRealm(), realmRep.getRealm() + "-realm.json"));
|
||||
}
|
||||
final AtomicBoolean realmImported = new AtomicBoolean();
|
||||
|
||||
new ExportImportSessionTask() {
|
||||
|
||||
@Override
|
||||
public void runExportImportTask(KeycloakSession session) {
|
||||
boolean imported = ImportUtils.importRealm(session, realmRep, strategy, true);
|
||||
realmImported.set(imported);
|
||||
ImportUtils.importRealm(session, realmRep, strategy, () -> {
|
||||
importUsers(realmName, userFiles, false);
|
||||
importUsers(realmName, federatedUserFiles, true);
|
||||
});
|
||||
}
|
||||
|
||||
}.runTask(factory);
|
||||
|
||||
if (realmImported.get()) {
|
||||
// Import users
|
||||
importUsers(realmName, userFiles, false);
|
||||
importUsers(realmName, federatedUserFiles, true);
|
||||
}
|
||||
|
||||
if (realmImported.get()) {
|
||||
// Import authorization and initialize service accounts last, as they require users already in DB
|
||||
new ExportImportSessionTask() {
|
||||
|
||||
@Override
|
||||
public void runExportImportTask(KeycloakSession session) {
|
||||
session.getContext().setRealm(session.realms().getRealmByName(realmName));
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
realmManager.setupClientServiceAccountsAndAuthorizationOnImport(realmRep, false);
|
||||
}
|
||||
|
||||
}.runTask(factory);
|
||||
}
|
||||
}
|
||||
|
||||
private void importUsers(final String realmName, File[] userFiles, boolean federated) {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class ImportUtils {
|
|||
// Import admin realm first
|
||||
for (RealmRepresentation realm : realms) {
|
||||
if (Config.getAdminRealm().equals(realm.getRealm())) {
|
||||
if (importRealm(session, realm, strategy, false)) {
|
||||
if (importRealm(session, realm, strategy, () -> {})) {
|
||||
masterImported = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ public class ImportUtils {
|
|||
|
||||
for (RealmRepresentation realm : realms) {
|
||||
if (!Config.getAdminRealm().equals(realm.getRealm())) {
|
||||
importRealm(session, realm, strategy, false);
|
||||
importRealm(session, realm, strategy, () -> {});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,8 +87,23 @@ public class ImportUtils {
|
|||
* @param strategy specifies whether to overwrite or ignore existing realm or user entries
|
||||
* @param skipUserDependent If true, then import of any models, which needs users already imported in DB, will be skipped. For example authorization
|
||||
* @return newly imported realm (or existing realm if ignoreExisting is true and realm of this name already exists)
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean importRealm(KeycloakSession session, RealmRepresentation rep, Strategy strategy, boolean skipUserDependent) {
|
||||
return importRealm(session, rep, strategy, skipUserDependent ? null : () -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully import realm from representation, save it to model and return model of newly created realm
|
||||
*
|
||||
* @param session
|
||||
* @param rep
|
||||
* @param strategy specifies whether to overwrite or ignore existing realm or user entries
|
||||
* @param userImport use null to indicate additional users will not be imported
|
||||
* @return newly imported realm (or existing realm if ignoreExisting is true and realm of this name already exists)
|
||||
*/
|
||||
public static boolean importRealm(KeycloakSession session, RealmRepresentation rep, Strategy strategy, Runnable userImport) {
|
||||
String realmName = rep.getRealm();
|
||||
RealmProvider model = session.realms();
|
||||
RealmModel realm = model.getRealmByName(realmName);
|
||||
|
|
@ -111,7 +126,7 @@ public class ImportUtils {
|
|||
}
|
||||
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
realmManager.importRealm(rep, skipUserDependent);
|
||||
realmManager.importRealm(rep, userImport);
|
||||
|
||||
if (System.getProperty(ExportImportConfig.ACTION) != null) {
|
||||
logger.infof("Realm '%s' imported", realmName);
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@ public class RepresentationToModel {
|
|||
public static final String OIDC = "openid-connect";
|
||||
|
||||
|
||||
public static void importRealm(KeycloakSession session, RealmRepresentation rep, RealmModel newRealm, boolean skipUserDependent) {
|
||||
session.getProvider(DatastoreProvider.class).getExportImportManager().importRealm(rep, newRealm, skipUserDependent);
|
||||
public static void importRealm(KeycloakSession session, RealmRepresentation rep, RealmModel newRealm, Runnable userImport) {
|
||||
session.getProvider(DatastoreProvider.class).getExportImportManager().importRealm(rep, newRealm, userImport);
|
||||
}
|
||||
|
||||
public static void importRoles(RolesRepresentation realmRoles, RealmModel realm) {
|
||||
|
|
@ -1180,7 +1180,7 @@ public class RepresentationToModel {
|
|||
if (applyPolicies != null && !applyPolicies.isEmpty()) {
|
||||
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||
try {
|
||||
List<String> policies = (List<String>) JsonSerialization.readValue(applyPolicies, List.class);
|
||||
List<String> policies = JsonSerialization.readValue(applyPolicies, List.class);
|
||||
Set<String> policyIds = new HashSet<>();
|
||||
|
||||
for (String policyName : policies) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import java.io.InputStream;
|
|||
* @author Alexander Schwartz
|
||||
*/
|
||||
public interface ExportImportManager {
|
||||
void importRealm(RealmRepresentation rep, RealmModel newRealm, boolean skipUserDependent);
|
||||
void importRealm(RealmRepresentation rep, RealmModel newRealm, Runnable userImport);
|
||||
|
||||
PartialImportResults partialImportRealm(RealmModel realm, InputStream requestBody);
|
||||
|
||||
|
|
|
|||
|
|
@ -527,14 +527,24 @@ public class RealmManager {
|
|||
}
|
||||
|
||||
public RealmModel importRealm(RealmRepresentation rep) {
|
||||
return importRealm(rep, false);
|
||||
return importRealm(rep, () -> {});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* if "skipUserDependent" is true, then import of any models, which needs users already imported in DB, will be skipped. For example authorization
|
||||
* @deprecated use {@link #importRealm(RealmRepresentation, Runnable)}
|
||||
*/
|
||||
@Deprecated
|
||||
public RealmModel importRealm(RealmRepresentation rep, boolean skipUserDependent) {
|
||||
return importRealm(rep, skipUserDependent ? null : () -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userImport if null, then import of any models, which needs users already imported in DB, will be skipped. For example authorization
|
||||
*/
|
||||
public RealmModel importRealm(RealmRepresentation rep, Runnable userImport) {
|
||||
boolean skipUserDependent = userImport == null;
|
||||
String id = rep.getId();
|
||||
if (id == null || id.trim().isEmpty()) {
|
||||
id = KeycloakModelUtils.generateId();
|
||||
|
|
@ -612,7 +622,7 @@ public class RealmManager {
|
|||
createDefaultClientScopes(realm);
|
||||
}
|
||||
|
||||
RepresentationToModel.importRealm(session, rep, realm, skipUserDependent);
|
||||
RepresentationToModel.importRealm(session, rep, realm, userImport);
|
||||
|
||||
setupClientServiceAccountsAndAuthorizationOnImport(rep, skipUserDependent);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import org.keycloak.admin.client.resource.OrganizationResource;
|
|||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.exportimport.ExportImportConfig;
|
||||
import org.keycloak.exportimport.dir.DirExportProviderFactory;
|
||||
import org.keycloak.exportimport.dir.DirImportProviderFactory;
|
||||
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
|
||||
import org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
|
|
@ -113,8 +115,21 @@ public class OrganizationExportTest extends AbstractOrganizationTest {
|
|||
}
|
||||
}
|
||||
|
||||
RealmRepresentation importedRealm = exportRemoveImportRealm();
|
||||
RealmRepresentation importedSingleFileRealm = exportRemoveImportRealm(true);
|
||||
|
||||
validateImported(expectedOrganizations, expectedManagedMembers, expectedUnmanagedMembers, importedSingleFileRealm);
|
||||
|
||||
testRealm().logoutAll();
|
||||
providerRealm.logoutAll();
|
||||
|
||||
RealmRepresentation importedDirRealm = exportRemoveImportRealm(false);
|
||||
|
||||
validateImported(expectedOrganizations, expectedManagedMembers, expectedUnmanagedMembers, importedDirRealm);
|
||||
}
|
||||
|
||||
private void validateImported(List<OrganizationRepresentation> expectedOrganizations,
|
||||
Map<String, List<String>> expectedManagedMembers, Map<String, List<String>> expectedUnmanagedMembers,
|
||||
RealmRepresentation importedRealm) {
|
||||
assertTrue(importedRealm.isOrganizationsEnabled());
|
||||
|
||||
List<OrganizationRepresentation> organizations = testRealm().organizations().list(-1, -1);
|
||||
|
|
@ -175,31 +190,44 @@ public class OrganizationExportTest extends AbstractOrganizationTest {
|
|||
List<OrganizationRepresentation> orgs = testRealm().organizations().list(-1, -1);
|
||||
assertEquals(1, orgs.size());
|
||||
|
||||
RealmRepresentation importedRealm = exportRemoveImportRealm();
|
||||
RealmRepresentation importedSingleFileRealm = exportRemoveImportRealm(true);
|
||||
|
||||
assertTrue(importedRealm.isOrganizationsEnabled());
|
||||
assertTrue(importedSingleFileRealm.isOrganizationsEnabled());
|
||||
|
||||
orgs = testRealm().organizations().list(-1, -1);
|
||||
assertEquals(1, orgs.size());
|
||||
assertEquals("acme", orgs.get(0).getName());
|
||||
}
|
||||
|
||||
private RealmRepresentation exportRemoveImportRealm() {
|
||||
//export
|
||||
private RealmRepresentation exportRemoveImportRealm(boolean file) {
|
||||
TestingExportImportResource exportImport = testingClient.testing().exportImport();
|
||||
exportImport.setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
|
||||
String fileOrDir;
|
||||
|
||||
//export
|
||||
if (file) {
|
||||
exportImport.setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
|
||||
fileOrDir = exportImport.getExportImportTestDirectory() + File.separator + "org-export.json";
|
||||
exportImport.setFile(fileOrDir);
|
||||
} else {
|
||||
exportImport.setProvider(DirExportProviderFactory.PROVIDER_ID);
|
||||
fileOrDir = exportImport.getExportImportTestDirectory();
|
||||
exportImport.setDir(fileOrDir);
|
||||
}
|
||||
exportImport.setAction(ExportImportConfig.ACTION_EXPORT);
|
||||
exportImport.setRealmName(testRealm().toRepresentation().getRealm());
|
||||
String targetFilePath = exportImport.getExportImportTestDirectory() + File.separator + "org-export.json";
|
||||
exportImport.setFile(targetFilePath);
|
||||
exportImport.runExport();
|
||||
|
||||
// remove the realm and import it back
|
||||
testRealm().remove();
|
||||
exportImport = testingClient.testing().exportImport();
|
||||
exportImport.setProvider(SingleFileImportProviderFactory.PROVIDER_ID);
|
||||
if (file) {
|
||||
exportImport.setProvider(SingleFileImportProviderFactory.PROVIDER_ID);
|
||||
exportImport.setFile(fileOrDir);
|
||||
} else {
|
||||
exportImport.setProvider(DirImportProviderFactory.PROVIDER_ID);
|
||||
exportImport.setDir(fileOrDir);
|
||||
}
|
||||
exportImport.setAction(ExportImportConfig.ACTION_IMPORT);
|
||||
exportImport.setFile(targetFilePath);
|
||||
exportImport.runImport();
|
||||
getCleanup().addCleanup(() -> {
|
||||
testRealm().remove();
|
||||
|
|
|
|||
Loading…
Reference in a new issue