/*
* 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.ImportProvider;
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.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.util.JsonSerialization;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DirImportProvider implements ImportProvider {
private static final Logger logger = Logger.getLogger(DirImportProvider.class);
private final File rootDirectory;
public DirImportProvider() {
// Determine system tmp directory
String tempDir = System.getProperty("java.io.tmpdir");
// Delete and recreate directory inside tmp
this.rootDirectory = new File(tempDir + "/keycloak-export");
if (!this.rootDirectory .exists()) {
throw new IllegalStateException("Directory " + this.rootDirectory + " doesn't exists");
}
logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath());
}
public DirImportProvider(File rootDirectory) {
this.rootDirectory = rootDirectory;
if (!this.rootDirectory.exists()) {
throw new IllegalStateException("Directory " + this.rootDirectory + " doesn't exists");
}
logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath());
}
@Override
public void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException {
List<String> realmNames = getRealmsToImport();
for (String realmName : realmNames) {
importRealm(factory, realmName, strategy);
}
}
@Override
public boolean isMasterRealmExported() throws IOException {
List<String> realmNames = getRealmsToImport();
return realmNames.contains(Config.getAdminRealm());
}
private List<String> getRealmsToImport() throws IOException {
File[] realmFiles = this.rootDirectory.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return (name.endsWith("-realm.json"));
}
});
List<String> realmNames = new ArrayList<String>();
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;
}
@Override
public void importRealm(KeycloakSessionFactory factory, final String realmName, final Strategy strategy) throws IOException {
File realmFile = new File(this.rootDirectory + File.separator + realmName + "-realm.json");
File[] userFiles = this.rootDirectory.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.matches(realmName + "-users-[0-9]+\\.json");
}
});
File[] federatedUserFiles = this.rootDirectory.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.matches(realmName + "-federated-users-[0-9]+\\.json");
}
});
// Import realm first
FileInputStream is = new FileInputStream(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) throws IOException {
boolean imported = ImportUtils.importRealm(session, realmRep, strategy, true);
realmImported.set(imported);
}
});
if (realmImported.get()) {
// Import users
for (final File userFile : userFiles) {
final FileInputStream fis = new FileInputStream(userFile);
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
protected void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
logger.infof("Imported users from %s", userFile.getAbsolutePath());
}
});
}
for (final File userFile : federatedUserFiles) {
final FileInputStream fis = new FileInputStream(userFile);
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
protected void runExportImportTask(KeycloakSession session) throws IOException {
ImportUtils.importFederatedUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
logger.infof("Imported federated users from %s", userFile.getAbsolutePath());
}
});
}
}
// Import authorization last, as authzPolicies can require users already in DB
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
public void runExportImportTask(KeycloakSession session) throws IOException {
RealmModel realm = session.realms().getRealmByName(realmName);
RepresentationToModel.importRealmAuthorizationSettings(realmRep, realm, session);
}
});
}
@Override
public void close() {
}
}