Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
8dcc909b6e
12 changed files with 150 additions and 76 deletions
|
@ -13,4 +13,10 @@ public interface ImportProvider extends Provider {
|
|||
void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException;
|
||||
|
||||
void importRealm(KeycloakSessionFactory factory, String realmName, Strategy strategy) throws IOException;
|
||||
|
||||
/**
|
||||
* @return true if master realm was previously exported and is available in the data to be imported
|
||||
* @throws IOException
|
||||
*/
|
||||
boolean isMasterRealmExported() throws IOException;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.keycloak.exportimport.ExportImportConfig;
|
||||
|
@ -143,6 +144,15 @@ public class ImportUtils {
|
|||
* @throws IOException
|
||||
*/
|
||||
public static void importFromStream(KeycloakSession session, ObjectMapper mapper, InputStream is, Strategy strategy) throws IOException {
|
||||
Map<String, RealmRepresentation> realmReps = getRealmsFromStream(mapper, is);
|
||||
for (RealmRepresentation realmRep : realmReps.values()) {
|
||||
importRealm(session, realmRep, strategy);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, RealmRepresentation> getRealmsFromStream(ObjectMapper mapper, InputStream is) throws IOException {
|
||||
Map<String, RealmRepresentation> result = new HashMap<String, RealmRepresentation>();
|
||||
|
||||
JsonFactory factory = mapper.getJsonFactory();
|
||||
JsonParser parser = factory.createJsonParser(is);
|
||||
try {
|
||||
|
@ -166,18 +176,21 @@ public class ImportUtils {
|
|||
}
|
||||
|
||||
for (RealmRepresentation realmRep : realmReps) {
|
||||
importRealm(session, realmRep, strategy);
|
||||
result.put(realmRep.getId(), realmRep);
|
||||
}
|
||||
} else if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
|
||||
// Case with single realm in stream
|
||||
RealmRepresentation realmRep = parser.readValueAs(RealmRepresentation.class);
|
||||
importRealm(session, realmRep, strategy);
|
||||
result.put(realmRep.getId(), realmRep);
|
||||
}
|
||||
} finally {
|
||||
parser.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Assuming that it's invoked inside transaction
|
||||
public static void importUsersFromStream(KeycloakSession session, String realmName, ObjectMapper mapper, InputStream is) throws IOException {
|
||||
RealmProvider model = session.realms();
|
||||
|
|
|
@ -49,6 +49,20 @@ public class DirImportProvider implements ImportProvider {
|
|||
|
||||
@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
|
||||
|
@ -70,10 +84,7 @@ public class DirImportProvider implements ImportProvider {
|
|||
realmNames.add(realmName);
|
||||
}
|
||||
}
|
||||
|
||||
for (String realmName : realmNames) {
|
||||
importRealm(factory, realmName, strategy);
|
||||
}
|
||||
return realmNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.exportimport.singlefile;
|
||||
|
||||
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;
|
||||
|
@ -8,11 +9,13 @@ 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.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -23,6 +26,9 @@ public class SingleFileImportProvider implements ImportProvider {
|
|||
|
||||
private File file;
|
||||
|
||||
// Allows to cache representation per provider to avoid parsing them twice
|
||||
protected Map<String, RealmRepresentation> realmReps;
|
||||
|
||||
public SingleFileImportProvider(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
@ -30,17 +36,33 @@ public class SingleFileImportProvider implements ImportProvider {
|
|||
@Override
|
||||
public void importModel(KeycloakSessionFactory factory, final Strategy strategy) throws IOException {
|
||||
logger.infof("Full importing from file %s", this.file.getAbsolutePath());
|
||||
checkRealmReps();
|
||||
|
||||
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
|
||||
|
||||
@Override
|
||||
protected void runExportImportTask(KeycloakSession session) throws IOException {
|
||||
FileInputStream is = new FileInputStream(file);
|
||||
ImportUtils.importFromStream(session, JsonSerialization.mapper, is, strategy);
|
||||
for (RealmRepresentation realmRep : realmReps.values()) {
|
||||
ImportUtils.importRealm(session, realmRep, strategy);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMasterRealmExported() throws IOException {
|
||||
checkRealmReps();
|
||||
return (realmReps.containsKey(Config.getAdminRealm()));
|
||||
}
|
||||
|
||||
protected void checkRealmReps() throws IOException {
|
||||
if (realmReps == null) {
|
||||
FileInputStream is = new FileInputStream(file);
|
||||
realmReps = ImportUtils.getRealmsFromStream(JsonSerialization.mapper, is);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importRealm(KeycloakSessionFactory factory, String realmName, Strategy strategy) throws IOException {
|
||||
// TODO: import just that single realm in case that file contains many realms?
|
||||
|
|
|
@ -51,6 +51,20 @@ public class ZipImportProvider implements ImportProvider {
|
|||
|
||||
@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 {
|
||||
List<String> realmNames = new ArrayList<String>();
|
||||
for (ExtZipEntry entry : this.decrypter.getEntryList()) {
|
||||
String entryName = entry.getName();
|
||||
|
@ -66,10 +80,7 @@ public class ZipImportProvider implements ImportProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String realmName : realmNames) {
|
||||
importRealm(factory, realmName, strategy);
|
||||
}
|
||||
return realmNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1649,7 +1649,8 @@ module.directive('kcProviderConfig', function ($modal) {
|
|||
scope: {
|
||||
config: '=',
|
||||
properties: '=',
|
||||
realm: '='
|
||||
realm: '=',
|
||||
clients: '='
|
||||
},
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
|
|
|
@ -45,28 +45,8 @@
|
|||
</div>
|
||||
<kc-tooltip>{{mapperType.helpText}}</kc-tooltip>
|
||||
</div>
|
||||
<div data-ng-repeat="option in mapperType.properties" class="form-group">
|
||||
<label class="col-md-2 control-label">{{option.label}}</label>
|
||||
|
||||
<div class="col-sm-4" data-ng-show="option.type == 'String'">
|
||||
<input class="form-control" type="text" data-ng-model="mapper.config[ option.name ]">
|
||||
</div>
|
||||
<div class="col-sm-4" data-ng-show="option.type == 'boolean'">
|
||||
<input ng-model="mapper.config[ option.name ]" value="'true'" name="option.name" id="option.name" onoffswitchmodel />
|
||||
</div>
|
||||
<div class="col-sm-4" data-ng-show="option.type == 'List'">
|
||||
<select ng-model="mapper.config[ option.name ]" ng-options="data for data in option.defaultValue">
|
||||
<option value="" selected> Select one... </option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-4" data-ng-show="option.type == 'ClientList'">
|
||||
<select ng-model="mapper.config[ option.name ]" ng-options="client.clientId as client.clientId for client in clients">
|
||||
<option value="" selected> Select one... </option>
|
||||
</select>
|
||||
</div>
|
||||
<kc-tooltip>{{option.helpText}}</kc-tooltip>
|
||||
</div>
|
||||
|
||||
<kc-provider-config config="mapper.config" properties="mapperType.properties" realm="realm" clients="clients"></kc-provider-config>
|
||||
</fieldset>
|
||||
<div class="pull-right form-actions" data-ng-show="create && access.manageRealm">
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div data-ng-repeat="option in properties" class="form-group">
|
||||
<label class="col-md-2 control-label">{{option.label}}</label>
|
||||
|
||||
<div class="col-sm-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role'">
|
||||
<div class="col-sm-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role' || option.type == 'ClientList'">
|
||||
<input class="form-control" type="text" data-ng-model="config[ option.name ]" >
|
||||
</div>
|
||||
<div class="col-sm-6" data-ng-show="option.type == 'boolean'">
|
||||
|
@ -18,6 +18,11 @@
|
|||
<div class="col-sm-4" data-ng-show="option.type == 'Role'">
|
||||
<button type="submit" data-ng-click="openRoleSelector(option.name)" class="btn btn-default" tooltip="Enter role in the textbox to the left, or click this button to browse and select the role you want">Select Role</button>
|
||||
</div>
|
||||
<div class="col-sm-4" data-ng-show="option.type == 'ClientList'">
|
||||
<select ng-model="config[ option.name ]" ng-options="client.clientId as client.clientId for client in clients">
|
||||
<option value="" selected> Select one... </option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<kc-tooltip>{{option.helpText}}</kc-tooltip>
|
||||
</div>
|
||||
|
|
|
@ -2,8 +2,10 @@ package org.keycloak.exportimport;
|
|||
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -12,7 +14,7 @@ public class ExportImportManager {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(ExportImportManager.class);
|
||||
|
||||
public void checkExportImport(KeycloakSessionFactory sessionFactory) {
|
||||
public void checkExportImport(KeycloakSessionFactory sessionFactory, String contextPath) {
|
||||
String exportImportAction = ExportImportConfig.getAction();
|
||||
String realmName = ExportImportConfig.getRealmName();
|
||||
|
||||
|
@ -46,9 +48,21 @@ public class ExportImportManager {
|
|||
Strategy strategy = ExportImportConfig.getStrategy();
|
||||
if (realmName == null) {
|
||||
logger.infof("Full model import requested. Strategy: %s", strategy.toString());
|
||||
|
||||
// Check if master realm was exported. If it's not, then it needs to be created before other realms are imported
|
||||
if (!importProvider.isMasterRealmExported()) {
|
||||
new ApplianceBootstrap().bootstrap(sessionFactory, contextPath);
|
||||
}
|
||||
|
||||
importProvider.importModel(sessionFactory, strategy);
|
||||
} else {
|
||||
logger.infof("Import of realm '%s' requested. Strategy: %s", realmName, strategy.toString());
|
||||
|
||||
if (!realmName.equals(Config.getAdminRealm())) {
|
||||
// Check if master realm exists. If it's not, then it needs to be created before other realm is imported
|
||||
new ApplianceBootstrap().bootstrap(sessionFactory, contextPath);
|
||||
}
|
||||
|
||||
importProvider.importRealm(sessionFactory, realmName, strategy);
|
||||
}
|
||||
logger.info("Import finished successfully");
|
|
@ -21,7 +21,9 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
|||
import org.keycloak.models.utils.DefaultRequiredActions;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.timer.TimerProvider;
|
||||
|
@ -267,13 +269,27 @@ public class RealmManager {
|
|||
setupMasterAdminManagement(realm);
|
||||
if (!hasRealmAdminManagementClient(rep)) setupRealmAdminManagement(realm);
|
||||
if (!hasAccountManagementClient(rep)) setupAccountManagement(realm);
|
||||
if (!hasImpersonationServiceClient(rep)) setupImpersonationService(realm);
|
||||
|
||||
boolean postponeImpersonationSetup = false;
|
||||
if (!hasImpersonationServiceClient(rep)) {
|
||||
if (hasRealmAdminManagementClient(rep)) {
|
||||
postponeImpersonationSetup = true;
|
||||
} else {
|
||||
setupImpersonationService(realm);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasBrokerClient(rep)) setupBrokerService(realm);
|
||||
if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm);
|
||||
|
||||
RepresentationToModel.importRealm(session, rep, realm);
|
||||
|
||||
// Could happen when migrating from older version and I have exported JSON file, which contains "realm-management" client but not "impersonation" client
|
||||
// I need to postpone impersonation because it needs "realm-management" client and it's roles set
|
||||
if (postponeImpersonationSetup) {
|
||||
setupImpersonationService(realm);
|
||||
}
|
||||
|
||||
setupAuthenticationFlows(realm);
|
||||
setupRequiredActions(realm);
|
||||
|
||||
|
@ -287,50 +303,49 @@ public class RealmManager {
|
|||
}
|
||||
|
||||
private boolean hasRealmAdminManagementClient(RealmRepresentation rep) {
|
||||
if (rep.getClients() == null) return false;
|
||||
for (ClientRepresentation clientRep : rep.getClients()) {
|
||||
if (clientRep.getClientId().equals(getRealmAdminClientId(rep))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
String realmAdminClientId = getRealmAdminClientId(rep);
|
||||
return hasClient(rep, realmAdminClientId);
|
||||
}
|
||||
|
||||
private boolean hasAccountManagementClient(RealmRepresentation rep) {
|
||||
if (rep.getClients() == null) return false;
|
||||
for (ClientRepresentation clientRep : rep.getClients()) {
|
||||
if (clientRep.getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return hasClient(rep, Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
|
||||
}
|
||||
private boolean hasImpersonationServiceClient(RealmRepresentation rep) {
|
||||
if (rep.getClients() == null) return false;
|
||||
for (ClientRepresentation clientRep : rep.getClients()) {
|
||||
if (clientRep.getClientId().equals(Constants.IMPERSONATION_SERVICE_CLIENT_ID)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return hasClient(rep, Constants.IMPERSONATION_SERVICE_CLIENT_ID);
|
||||
}
|
||||
private boolean hasBrokerClient(RealmRepresentation rep) {
|
||||
if (rep.getClients() == null) return false;
|
||||
for (ClientRepresentation clientRep : rep.getClients()) {
|
||||
if (clientRep.getClientId().equals(Constants.BROKER_SERVICE_CLIENT_ID)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return hasClient(rep, Constants.BROKER_SERVICE_CLIENT_ID);
|
||||
}
|
||||
|
||||
private boolean hasAdminConsoleClient(RealmRepresentation rep) {
|
||||
if (rep.getClients() == null) return false;
|
||||
return hasClient(rep, Constants.ADMIN_CONSOLE_CLIENT_ID);
|
||||
}
|
||||
|
||||
private boolean hasClient(RealmRepresentation rep, String clientId) {
|
||||
if (rep.getClients() != null) {
|
||||
for (ClientRepresentation clientRep : rep.getClients()) {
|
||||
if (clientRep.getClientId().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) {
|
||||
if (clientRep.getClientId().equals(clientId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Just for compatibility with old versions. Should be removed later...
|
||||
if (rep.getApplications() != null) {
|
||||
for (ApplicationRepresentation clientRep : rep.getApplications()) {
|
||||
if (clientRep.getName().equals(clientId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rep.getOauthClients() != null) {
|
||||
for (OAuthClientRepresentation clientRep : rep.getOauthClients()) {
|
||||
if (clientRep.getName().equals(clientId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ public class KeycloakApplication extends Application {
|
|||
classes.add(JsResource.class);
|
||||
classes.add(WelcomeResource.class);
|
||||
|
||||
new ExportImportManager().checkExportImport(this.sessionFactory);
|
||||
new ExportImportManager().checkExportImport(this.sessionFactory, context.getContextPath());
|
||||
|
||||
setupDefaultRealm(context.getContextPath());
|
||||
|
||||
|
|
|
@ -168,12 +168,8 @@ public class RealmsAdminResource {
|
|||
for (InputPart inputPart : inputParts) {
|
||||
// inputPart.getBody doesn't work as content-type is wrong, and inputPart.setMediaType is not supported on AS7 (RestEasy 2.3.2.Final)
|
||||
rep = JsonSerialization.readValue(inputPart.getBodyAsString(), RealmRepresentation.class);
|
||||
RealmModel realm;
|
||||
try {
|
||||
realm = realmManager.importRealm(rep);
|
||||
} catch (ModelDuplicateException e) {
|
||||
return ErrorResponse.exists("Realm " + rep.getRealm() + " already exists");
|
||||
}
|
||||
|
||||
RealmModel realm = realmManager.importRealm(rep);
|
||||
|
||||
grantPermissionsToRealmCreator(realm);
|
||||
|
||||
|
|
Loading…
Reference in a new issue