DirImportProvider.java
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.exportimport.dir;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.exportimport.AbstractFileBasedImportProvider;
import org.keycloak.exportimport.Strategy;
import org.keycloak.exportimport.util.ExportImportSessionTask;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.platform.Platform;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.ServicesLogger;
import org.keycloak.util.JsonSerialization;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import org.keycloak.services.managers.RealmManager;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DirImportProvider extends AbstractFileBasedImportProvider {
private final Strategy strategy;
private final KeycloakSessionFactory factory;
private static final Logger logger = Logger.getLogger(DirImportProvider.class);
private File rootDirectory;
private String realmName;
public DirImportProvider(KeycloakSessionFactory factory, Strategy strategy) {
this.factory = factory;
this.strategy = strategy;
}
public DirImportProvider withDir(String dir) {
this.rootDirectory = new File(dir);
if (!this.rootDirectory.exists()) {
throw new IllegalStateException("Directory " + this.rootDirectory + " doesn't exist");
}
logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath());
return this;
}
public DirImportProvider withRealmName(String realmName) {
this.realmName = realmName;
return this;
}
private File getRootDirectory() {
if (rootDirectory == null) {
this.rootDirectory = new File(Platform.getPlatform().getTmpDirectory(), "keycloak-export");
if (!this.rootDirectory.exists()) {
throw new IllegalStateException("Directory " + this.rootDirectory + " doesn't exist");
}
logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath());
}
return rootDirectory;
}
@Override
public void importModel() throws IOException {
if (realmName != null) {
ServicesLogger.LOGGER.realmImportRequested(realmName, strategy.toString());
importRealm(realmName, strategy);
} else {
ServicesLogger.LOGGER.fullModelImport(strategy.toString());
List<String> realmNames = getRealmsToImport();
for (String realmName : realmNames) {
importRealm(realmName, strategy);
}
}
ServicesLogger.LOGGER.importSuccess();
}
@Override
public boolean isMasterRealmExported() {
List<String> realmNames = getRealmsToImport();
return realmNames.contains(Config.getAdminRealm());
}
private List<String> getRealmsToImport() {
File[] realmFiles = getRootDirectory().listFiles((dir, name) -> (name.endsWith("-realm.json")));
Objects.requireNonNull(realmFiles, "Directory not found: " + getRootDirectory().getName());
List<String> realmNames = new ArrayList<>();
for (File file : realmFiles) {
String fileName = file.getName();
// Parse "foo" from "foo-realm.json"
String realmName = fileName.substring(0, fileName.length() - 11);
// Ensure that master realm is imported first
if (Config.getAdminRealm().equals(realmName)) {
realmNames.add(0, realmName);
} else {
realmNames.add(realmName);
}
}
return realmNames;
}
public void importRealm(final String realmName, final Strategy strategy) throws IOException {
File realmFile = new File(getRootDirectory() + File.separator + realmName + "-realm.json");
File[] userFiles = getRootDirectory().listFiles((dir, name) -> name.matches(realmName + "-users-[0-9]+\\.json"));
Objects.requireNonNull(userFiles, "directory not found: " + getRootDirectory().getName());
File[] federatedUserFiles = getRootDirectory().listFiles((dir, name) -> name.matches(realmName + "-federated-users-[0-9]+\\.json"));
Objects.requireNonNull(federatedUserFiles, "directory not found: " + getRootDirectory().getName());
// Import realm first
InputStream is = parseFile(realmFile);
final RealmRepresentation realmRep = JsonSerialization.readValue(is, RealmRepresentation.class);
final AtomicBoolean realmImported = new AtomicBoolean();
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
public void runExportImportTask(KeycloakSession session) {
boolean imported = ImportUtils.importRealm(session, realmRep, strategy, true);
realmImported.set(imported);
}
});
if (realmImported.get()) {
// Import users
for (final File userFile : userFiles) {
try (InputStream fis = parseFile(userFile)) {
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
protected void runExportImportTask(KeycloakSession session) throws IOException {
session.getContext().setRealm(session.realms().getRealmByName(realmName));
ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
logger.infof("Imported users from %s", userFile.getAbsolutePath());
}
});
}
}
for (final File userFile : federatedUserFiles) {
try (InputStream fis = parseFile(userFile)) {
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
protected void runExportImportTask(KeycloakSession session) throws IOException {
session.getContext().setRealm(session.realms().getRealmByName(realmName));
ImportUtils.importFederatedUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
logger.infof("Imported federated users from %s", userFile.getAbsolutePath());
}
});
}
}
}
if (realmImported.get()) {
// Import authorization and initialize service accounts last, as they require users already in DB
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
public void runExportImportTask(KeycloakSession session) {
session.getContext().setRealm(session.realms().getRealmByName(realmName));
RealmManager realmManager = new RealmManager(session);
realmManager.setupClientServiceAccountsAndAuthorizationOnImport(realmRep, false);
}
});
}
}
@Override
public void close() {
}
}