Clean commit for partial import with single page for all imports.
This commit is contained in:
parent
1e3ec1983d
commit
f6a02bd408
23 changed files with 1763 additions and 98 deletions
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.representations.idm;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for partial import of users, clients, and identity providers.
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown=true)
|
||||||
|
public class PartialImportRepresentation {
|
||||||
|
public enum Policy { SKIP, OVERWRITE, FAIL };
|
||||||
|
|
||||||
|
protected Policy policy = Policy.FAIL;
|
||||||
|
protected String ifResourceExists = "";
|
||||||
|
protected List<UserRepresentation> users;
|
||||||
|
protected List<ClientRepresentation> clients;
|
||||||
|
protected List<IdentityProviderRepresentation> identityProviders;
|
||||||
|
protected RolesRepresentation roles;
|
||||||
|
|
||||||
|
public boolean hasUsers() {
|
||||||
|
return (users != null) && !users.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasClients() {
|
||||||
|
return (clients != null) && !clients.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasIdps() {
|
||||||
|
return (identityProviders != null) && !identityProviders.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIfResourceExists() {
|
||||||
|
return ifResourceExists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIfResourceExists(String ifResourceExists) {
|
||||||
|
this.ifResourceExists = ifResourceExists;
|
||||||
|
this.policy = Policy.valueOf(ifResourceExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Policy getPolicy() {
|
||||||
|
return this.policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UserRepresentation> getUsers() {
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsers(List<UserRepresentation> users) {
|
||||||
|
this.users = users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ClientRepresentation> getClients() {
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClients(List<ClientRepresentation> clients) {
|
||||||
|
this.clients = clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IdentityProviderRepresentation> getIdentityProviders() {
|
||||||
|
return identityProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdentityProviders(List<IdentityProviderRepresentation> identityProviders) {
|
||||||
|
this.identityProviders = identityProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RolesRepresentation getRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoles(RolesRepresentation roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
}
|
|
@ -406,6 +406,16 @@ module.config([ '$routeProvider', function($routeProvider) {
|
||||||
},
|
},
|
||||||
controller : 'RealmEventsConfigCtrl'
|
controller : 'RealmEventsConfigCtrl'
|
||||||
})
|
})
|
||||||
|
.when('/realms/:realm/partial-import', {
|
||||||
|
templateUrl : resourceUrl + '/partials/partial-import.html',
|
||||||
|
resolve : {
|
||||||
|
resourceName : function() { return 'users'},
|
||||||
|
realm : function(RealmLoader) {
|
||||||
|
return RealmLoader();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
controller : 'RealmImportCtrl'
|
||||||
|
})
|
||||||
.when('/create/user/:realm', {
|
.when('/create/user/:realm', {
|
||||||
templateUrl : resourceUrl + '/partials/user-detail.html',
|
templateUrl : resourceUrl + '/partials/user-detail.html',
|
||||||
resolve : {
|
resolve : {
|
||||||
|
|
|
@ -2062,14 +2062,162 @@ module.controller('ClientInitialAccessCreateCtrl', function($scope, realm, Clien
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.controller('RealmImportCtrl', function($scope, realm, $route,
|
||||||
|
Notifications, $modal, $resource) {
|
||||||
|
$scope.rawContent = {};
|
||||||
|
$scope.fileContent = {
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
$scope.changed = false;
|
||||||
|
$scope.files = [];
|
||||||
|
$scope.realm = realm;
|
||||||
|
$scope.overwrite = false;
|
||||||
|
$scope.skip = false;
|
||||||
|
$scope.importUsers = false;
|
||||||
|
$scope.importClients = false;
|
||||||
|
$scope.importIdentityProviders = false;
|
||||||
|
$scope.importRealmRoles = false;
|
||||||
|
$scope.importClientRoles = false;
|
||||||
|
$scope.ifResourceExists='FAIL';
|
||||||
|
$scope.isMultiRealm = false;
|
||||||
|
$scope.results = {};
|
||||||
|
|
||||||
|
var oldCopy = angular.copy($scope.fileContent);
|
||||||
|
|
||||||
|
$scope.importFile = function($fileContent){
|
||||||
|
var parsed;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse($fileContent);
|
||||||
|
} catch (e) {
|
||||||
|
Notifications.error('Unable to parse JSON file.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.rawContent = angular.copy(parsed);
|
||||||
|
if (($scope.rawContent instanceof Array) && ($scope.rawContent.length > 0)) {
|
||||||
|
if ($scope.rawContent.length > 1) $scope.isMultiRealm = true;
|
||||||
|
$scope.fileContent = $scope.rawContent[0];
|
||||||
|
} else {
|
||||||
|
$scope.fileContent = $scope.rawContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.importing = true;
|
||||||
|
$scope.importUsers = $scope.hasArray('users');
|
||||||
|
$scope.importClients = $scope.hasArray('clients');
|
||||||
|
$scope.importIdentityProviders = $scope.hasArray('identityProviders');
|
||||||
|
$scope.importRealmRoles = $scope.hasRealmRoles();
|
||||||
|
$scope.importClientRoles = $scope.hasClientRoles();
|
||||||
|
$scope.results = {};
|
||||||
|
if (!$scope.hasResources()) {
|
||||||
|
$scope.nothingToImport();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.hasResults = function() {
|
||||||
|
return (Object.keys($scope.results).length > 0) &&
|
||||||
|
($scope.results.results !== 'undefined') &&
|
||||||
|
($scope.results.results.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.viewImportDetails = function() {
|
||||||
|
$modal.open({
|
||||||
|
templateUrl: resourceUrl + '/partials/modal/view-object.html',
|
||||||
|
controller: 'ObjectModalCtrl',
|
||||||
|
resolve: {
|
||||||
|
object: function () {
|
||||||
|
return $scope.fileContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.hasArray = function(section) {
|
||||||
|
return ($scope.fileContent !== 'undefined') &&
|
||||||
|
($scope.fileContent.hasOwnProperty(section)) &&
|
||||||
|
($scope.fileContent[section] instanceof Array) &&
|
||||||
|
($scope.fileContent[section].length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.hasRealmRoles = function() {
|
||||||
|
return $scope.hasRoles() &&
|
||||||
|
($scope.fileContent.roles.hasOwnProperty('realm')) &&
|
||||||
|
($scope.fileContent.roles.realm instanceof Array) &&
|
||||||
|
($scope.fileContent.roles.realm.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.hasRoles = function() {
|
||||||
|
return ($scope.fileContent !== 'undefined') &&
|
||||||
|
($scope.fileContent.hasOwnProperty('roles')) &&
|
||||||
|
($scope.fileContent.roles !== 'undefined');
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.hasClientRoles = function() {
|
||||||
|
return $scope.hasRoles() &&
|
||||||
|
($scope.fileContent.roles.hasOwnProperty('client')) &&
|
||||||
|
(Object.keys($scope.fileContent.roles.client).length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.itemCount = function(section) {
|
||||||
|
if (!$scope.importing) return 0;
|
||||||
|
if ($scope.hasRealmRoles() && (section === 'roles.realm')) return $scope.fileContent.roles.realm.length;
|
||||||
|
if ($scope.hasClientRoles() && (section === 'roles.client')) return Object.keys($scope.fileContent.roles.client).length;
|
||||||
|
|
||||||
|
if (!$scope.fileContent.hasOwnProperty(section)) return 0;
|
||||||
|
|
||||||
|
return $scope.fileContent[section].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.hasResources = function() {
|
||||||
|
return ($scope.importUsers && $scope.hasArray('users')) ||
|
||||||
|
($scope.importClients && $scope.hasArray('clients')) ||
|
||||||
|
($scope.importIdentityProviders && $scope.hasArray('identityProviders')) ||
|
||||||
|
($scope.importRealmRoles && $scope.hasRealmRoles()) ||
|
||||||
|
($scope.importClientRoles && $scope.hasClientRoles());
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.nothingToImport = function() {
|
||||||
|
Notifications.error('No resouces specified to import.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$watch('fileContent', function() {
|
||||||
|
if (!angular.equals($scope.fileContent, oldCopy)) {
|
||||||
|
$scope.changed = true;
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
$scope.save = function() {
|
||||||
|
var json = angular.copy($scope.fileContent);
|
||||||
|
json.ifResourceExists = $scope.ifResourceExists;
|
||||||
|
if (!$scope.importUsers) delete json.users;
|
||||||
|
if (!$scope.importIdentityProviders) delete json.identityProviders;
|
||||||
|
if (!$scope.importClients) delete json.clients;
|
||||||
|
|
||||||
|
if (json.hasOwnProperty('roles')) {
|
||||||
|
if (!$scope.importRealmRoles) delete json.roles.realm;
|
||||||
|
if (!$scope.importClientRoles) delete json.roles.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
var importFile = $resource(authUrl + '/admin/realms/' + realm.realm + '/partialImport');
|
||||||
|
$scope.results = importFile.save(json, function() {
|
||||||
|
var message = $scope.results.added + ' records added. ';
|
||||||
|
if ($scope.ifResourceExists === 'SKIP') {
|
||||||
|
message += $scope.results.skipped + ' records skipped.'
|
||||||
|
}
|
||||||
|
if ($scope.ifResourceExists === 'OVERWRITE') {
|
||||||
|
message += $scope.results.overwritten + ' records overwritten.';
|
||||||
|
}
|
||||||
|
Notifications.success(message);
|
||||||
|
}, function(error) {
|
||||||
|
if (error.data.errorMessage) {
|
||||||
|
Notifications.error(error.data.errorMessage);
|
||||||
|
} else {
|
||||||
|
Notifications.error('Unexpected error during import');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.reset = function() {
|
||||||
|
$route.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||||
|
|
||||||
|
<h1>Partial Import</h1>
|
||||||
|
|
||||||
|
<form class="form-horizontal" name="partialImportForm" novalidate>
|
||||||
|
<fieldset class="border-top">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name" class="col-sm-2 control-label">File</label>
|
||||||
|
|
||||||
|
<div class="col-md-6" data-ng-hide="importing">
|
||||||
|
<label for="import-file" class="btn btn-default">{{:: 'select-file'| translate}} <i class="pficon pficon-import"></i></label>
|
||||||
|
<input id="import-file" type="file" class="hidden" kc-on-read-file="importFile($fileContent)"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6" data-ng-show="importing">
|
||||||
|
<button class="btn btn-default" data-ng-click="viewImportDetails()">{{:: 'view-details'| translate}}</button>
|
||||||
|
<button class="btn btn-default" data-ng-click="reset()">{{:: 'clear-import'| translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="importing && isMultiRealm && !hasResults()">
|
||||||
|
<label for="fromRealm" class="col-md-2 control-label">Import from realm</label>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div>
|
||||||
|
<select id="fromRealm" ng-model="fileContent" class="form-control"
|
||||||
|
ng-options="item as item.realm for item in rawContent">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="importing && hasArray('users') && !hasResults()">
|
||||||
|
<label class="col-md-2 control-label" for="importUsers">Import Users ({{itemCount('users')}})</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input ng-model="importUsers" name="importUsers" id="importUsers" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="importing && hasArray('clients') && !hasResults()">
|
||||||
|
<label class="col-md-2 control-label" for="importClients">Import Clients ({{itemCount('clients')}})</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input ng-model="importClients" name="importClients" id="importClients" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="importing && hasArray('identityProviders') && !hasResults()">
|
||||||
|
<label class="col-md-2 control-label" for="importIdentityProviders">Import Identity Providers ({{itemCount('identityProviders')}})</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input ng-model="importIdentityProviders" name="importIdentityProviders" id="importIdentityProviders" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="importing && hasRealmRoles() && !hasResults()">
|
||||||
|
<label class="col-md-2 control-label" for="importRealmRoles">Import Realm Roles ({{itemCount('roles.realm')}})</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input ng-model="importRealmRoles" name="importRealmRoles" id="importRealmRoles" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="importing && hasClientRoles() && !hasResults()">
|
||||||
|
<label class="col-md-2 control-label" for="importClientRoles">Import Client Roles ({{itemCount('roles.client')}})</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input ng-model="importClientRoles" name="importClientRoles" id="importClientRoles" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="importing && hasResources() && !hasResults()">
|
||||||
|
<label for="ifResourceExists" class="col-md-2 control-label">If a resource exists</label>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div>
|
||||||
|
<select id="ifResourceExists" ng-model="ifResourceExists" class="form-control">
|
||||||
|
<option value="FAIL">Fail</option>
|
||||||
|
<option value="SKIP">Skip</option>
|
||||||
|
<option value="OVERWRITE">Overwrite</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>Specify what should be done if you try to import a resource that already exists.</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="importing && hasResources() && !hasResults()">
|
||||||
|
<div class="col-md-10 col-md-offset-2">
|
||||||
|
<button kc-save data-ng-disabled="!changed">{{:: 'import'| translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" data-ng-show="hasResults()">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Action</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="result in results.results" >
|
||||||
|
<td>{{result.action}}</td>
|
||||||
|
<td>{{result.resourceType}}</td>
|
||||||
|
<td>{{result.resourceName}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kc-menu></kc-menu>
|
|
@ -45,6 +45,7 @@
|
||||||
<li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-user"></span> Users</a></li>
|
<li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users"><span class="pficon pficon-user"></span> Users</a></li>
|
||||||
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm"><i class="fa fa-clock-o"></i> Sessions</a></li>
|
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'sessions') && 'active'"><a href="#/realms/{{realm.realm}}/sessions/realm"><i class="fa fa-clock-o"></i> Sessions</a></li>
|
||||||
<li data-ng-show="access.viewEvents" data-ng-class="(path[2] == 'events' || path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events"><i class="fa fa-calendar"></i> Events</a></li>
|
<li data-ng-show="access.viewEvents" data-ng-class="(path[2] == 'events' || path[2] == 'events-settings') && 'active'"><a href="#/realms/{{realm.realm}}/events"><i class="fa fa-calendar"></i> Events</a></li>
|
||||||
|
<li data-ng-show="access.manageRealm" ng-class="(path[2] =='partial-import') && 'active'"><a href="#/realms/{{realm.realm}}/partial-import"><span class="pficon pficon-import"></span> Import</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -65,6 +65,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import org.keycloak.representations.idm.RolesRepresentation;
|
||||||
|
|
||||||
public class RepresentationToModel {
|
public class RepresentationToModel {
|
||||||
|
|
||||||
|
@ -194,47 +195,7 @@ public class RepresentationToModel {
|
||||||
createClients(session, rep, newRealm);
|
createClients(session, rep, newRealm);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rep.getRoles() != null) {
|
importRoles(rep.getRoles(), newRealm);
|
||||||
if (rep.getRoles().getRealm() != null) { // realm roles
|
|
||||||
for (RoleRepresentation roleRep : rep.getRoles().getRealm()) {
|
|
||||||
createRole(newRealm, roleRep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rep.getRoles().getClient() != null) {
|
|
||||||
for (Map.Entry<String, List<RoleRepresentation>> entry : rep.getRoles().getClient().entrySet()) {
|
|
||||||
ClientModel client = newRealm.getClientByClientId(entry.getKey());
|
|
||||||
if (client == null) {
|
|
||||||
throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey());
|
|
||||||
}
|
|
||||||
for (RoleRepresentation roleRep : entry.getValue()) {
|
|
||||||
// Application role may already exists (for example if it is defaultRole)
|
|
||||||
RoleModel role = roleRep.getId()!=null ? client.addRole(roleRep.getId(), roleRep.getName()) : client.addRole(roleRep.getName());
|
|
||||||
role.setDescription(roleRep.getDescription());
|
|
||||||
boolean scopeParamRequired = roleRep.isScopeParamRequired()==null ? false : roleRep.isScopeParamRequired();
|
|
||||||
role.setScopeParamRequired(scopeParamRequired);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// now that all roles are created, re-iterate and set up composites
|
|
||||||
if (rep.getRoles().getRealm() != null) { // realm roles
|
|
||||||
for (RoleRepresentation roleRep : rep.getRoles().getRealm()) {
|
|
||||||
RoleModel role = newRealm.getRole(roleRep.getName());
|
|
||||||
addComposites(role, roleRep, newRealm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rep.getRoles().getClient() != null) {
|
|
||||||
for (Map.Entry<String, List<RoleRepresentation>> entry : rep.getRoles().getClient().entrySet()) {
|
|
||||||
ClientModel client = newRealm.getClientByClientId(entry.getKey());
|
|
||||||
if (client == null) {
|
|
||||||
throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey());
|
|
||||||
}
|
|
||||||
for (RoleRepresentation roleRep : entry.getValue()) {
|
|
||||||
RoleModel role = client.getRole(roleRep.getName());
|
|
||||||
addComposites(role, roleRep, newRealm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup realm default roles
|
// Setup realm default roles
|
||||||
if (rep.getDefaultRoles() != null) {
|
if (rep.getDefaultRoles() != null) {
|
||||||
|
@ -355,6 +316,50 @@ public class RepresentationToModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void importRoles(RolesRepresentation realmRoles, RealmModel realm) {
|
||||||
|
if (realmRoles == null) return;
|
||||||
|
|
||||||
|
if (realmRoles.getRealm() != null) { // realm roles
|
||||||
|
for (RoleRepresentation roleRep : realmRoles.getRealm()) {
|
||||||
|
createRole(realm, roleRep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (realmRoles.getClient() != null) {
|
||||||
|
for (Map.Entry<String, List<RoleRepresentation>> entry : realmRoles.getClient().entrySet()) {
|
||||||
|
ClientModel client = realm.getClientByClientId(entry.getKey());
|
||||||
|
if (client == null) {
|
||||||
|
throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey());
|
||||||
|
}
|
||||||
|
for (RoleRepresentation roleRep : entry.getValue()) {
|
||||||
|
// Application role may already exists (for example if it is defaultRole)
|
||||||
|
RoleModel role = roleRep.getId()!=null ? client.addRole(roleRep.getId(), roleRep.getName()) : client.addRole(roleRep.getName());
|
||||||
|
role.setDescription(roleRep.getDescription());
|
||||||
|
boolean scopeParamRequired = roleRep.isScopeParamRequired()==null ? false : roleRep.isScopeParamRequired();
|
||||||
|
role.setScopeParamRequired(scopeParamRequired);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// now that all roles are created, re-iterate and set up composites
|
||||||
|
if (realmRoles.getRealm() != null) { // realm roles
|
||||||
|
for (RoleRepresentation roleRep : realmRoles.getRealm()) {
|
||||||
|
RoleModel role = realm.getRole(roleRep.getName());
|
||||||
|
addComposites(role, roleRep, realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (realmRoles.getClient() != null) {
|
||||||
|
for (Map.Entry<String, List<RoleRepresentation>> entry : realmRoles.getClient().entrySet()) {
|
||||||
|
ClientModel client = realm.getClientByClientId(entry.getKey());
|
||||||
|
if (client == null) {
|
||||||
|
throw new RuntimeException("App doesn't exist in role definitions: " + entry.getKey());
|
||||||
|
}
|
||||||
|
for (RoleRepresentation roleRep : entry.getValue()) {
|
||||||
|
RoleModel role = client.getRole(roleRep.getName());
|
||||||
|
addComposites(role, roleRep, realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void importGroups(RealmModel realm, RealmRepresentation rep) {
|
public static void importGroups(RealmModel realm, RealmRepresentation rep) {
|
||||||
List<GroupRepresentation> groups = rep.getGroups();
|
List<GroupRepresentation> groups = rep.getGroups();
|
||||||
if (groups == null) return;
|
if (groups == null) return;
|
||||||
|
@ -631,15 +636,15 @@ public class RepresentationToModel {
|
||||||
if (rep.getAccountTheme() != null) realm.setAccountTheme(rep.getAccountTheme());
|
if (rep.getAccountTheme() != null) realm.setAccountTheme(rep.getAccountTheme());
|
||||||
if (rep.getAdminTheme() != null) realm.setAdminTheme(rep.getAdminTheme());
|
if (rep.getAdminTheme() != null) realm.setAdminTheme(rep.getAdminTheme());
|
||||||
if (rep.getEmailTheme() != null) realm.setEmailTheme(rep.getEmailTheme());
|
if (rep.getEmailTheme() != null) realm.setEmailTheme(rep.getEmailTheme());
|
||||||
|
|
||||||
if (rep.isEventsEnabled() != null) realm.setEventsEnabled(rep.isEventsEnabled());
|
if (rep.isEventsEnabled() != null) realm.setEventsEnabled(rep.isEventsEnabled());
|
||||||
if (rep.getEventsExpiration() != null) realm.setEventsExpiration(rep.getEventsExpiration());
|
if (rep.getEventsExpiration() != null) realm.setEventsExpiration(rep.getEventsExpiration());
|
||||||
if (rep.getEventsListeners() != null) realm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
|
if (rep.getEventsListeners() != null) realm.setEventsListeners(new HashSet<>(rep.getEventsListeners()));
|
||||||
if (rep.getEnabledEventTypes() != null) realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
|
if (rep.getEnabledEventTypes() != null) realm.setEnabledEventTypes(new HashSet<>(rep.getEnabledEventTypes()));
|
||||||
|
|
||||||
if (rep.isAdminEventsEnabled() != null) realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
|
if (rep.isAdminEventsEnabled() != null) realm.setAdminEventsEnabled(rep.isAdminEventsEnabled());
|
||||||
if (rep.isAdminEventsDetailsEnabled() != null) realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
|
if (rep.isAdminEventsDetailsEnabled() != null) realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
|
||||||
|
|
||||||
|
|
||||||
if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
|
if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
|
||||||
if (rep.getOtpPolicyType() != null) realm.setOTPPolicy(toPolicy(rep));
|
if (rep.getOtpPolicyType() != null) realm.setOTPPolicy(toPolicy(rep));
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractPartialImport<T> implements PartialImport {
|
||||||
|
|
||||||
|
public abstract List<T> getRepList(PartialImportRepresentation partialImportRep);
|
||||||
|
public abstract String getName(T resourceRep);
|
||||||
|
public abstract boolean exists(RealmModel realm, KeycloakSession session, T resourceRep);
|
||||||
|
public abstract String existsMessage(T resourceRep);
|
||||||
|
public abstract ResourceType getResourceType();
|
||||||
|
public abstract void overwrite(RealmModel realm, KeycloakSession session, T resourceRep);
|
||||||
|
public abstract void create(RealmModel realm, KeycloakSession session, T resourceRep);
|
||||||
|
|
||||||
|
protected void prepare(PartialImportRepresentation partialImportRep,
|
||||||
|
RealmModel realm,
|
||||||
|
KeycloakSession session,
|
||||||
|
Set<T> resourcesToOverwrite,
|
||||||
|
Set<T> resourcesToSkip) throws ErrorResponseException {
|
||||||
|
for (T resourceRep : getRepList(partialImportRep)) {
|
||||||
|
if (exists(realm, session, resourceRep)) {
|
||||||
|
switch (partialImportRep.getPolicy()) {
|
||||||
|
case SKIP: resourcesToSkip.add(resourceRep); break;
|
||||||
|
case OVERWRITE: resourcesToOverwrite.add(resourceRep); break;
|
||||||
|
default: throw exists(existsMessage(resourceRep));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ErrorResponseException exists(String message) {
|
||||||
|
Response error = ErrorResponse.exists(message);
|
||||||
|
return new ErrorResponseException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PartialImportResult overwritten(T resourceRep){
|
||||||
|
return PartialImportResult.overwritten(getResourceType(), getName(resourceRep), resourceRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PartialImportResult skipped(T resourceRep) {
|
||||||
|
return PartialImportResult.skipped(getResourceType(), getName(resourceRep), resourceRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PartialImportResult added(T resourceRep) {
|
||||||
|
return PartialImportResult.added(getResourceType(), getName(resourceRep), resourceRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartialImportResults doImport(PartialImportRepresentation partialImportRep, RealmModel realm, KeycloakSession session) throws ErrorResponseException {
|
||||||
|
PartialImportResults results = new PartialImportResults();
|
||||||
|
List<T> repList = getRepList(partialImportRep);
|
||||||
|
if ((repList == null) || repList.isEmpty()) return results;
|
||||||
|
|
||||||
|
final Set<T> toOverwrite = new HashSet<>();
|
||||||
|
final Set<T> toSkip = new HashSet<>();
|
||||||
|
prepare(partialImportRep, realm, session, toOverwrite, toSkip);
|
||||||
|
|
||||||
|
for (T resourceRep: toOverwrite) {
|
||||||
|
System.out.println("overwriting " + getResourceType() + " " + getName(resourceRep));
|
||||||
|
try {
|
||||||
|
overwrite(realm, session, resourceRep);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ErrorResponseException(ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
results.addResult(overwritten(resourceRep));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (T resourceRep : toSkip) {
|
||||||
|
System.out.println("skipping " + getResourceType() + " " + getName(resourceRep));
|
||||||
|
results.addResult(skipped(resourceRep));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (T resourceRep : repList) {
|
||||||
|
if (toOverwrite.contains(resourceRep)) continue;
|
||||||
|
if (toSkip.contains(resourceRep)) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
System.out.println("adding " + getResourceType() + " " + getName(resourceRep));
|
||||||
|
create(realm, session, resourceRep);
|
||||||
|
results.addResult(added(resourceRep));
|
||||||
|
} catch (Exception e) {
|
||||||
|
//e.printStackTrace();
|
||||||
|
throw new ErrorResponseException(ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ssilvert
|
||||||
|
*/
|
||||||
|
public enum Action {
|
||||||
|
ADDED, SKIPPED, OVERWRITTEN
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class ClientRolesPartialImport implements PartialImport {
|
||||||
|
|
||||||
|
public Map<String, List<RoleRepresentation>> getRepList(PartialImportRepresentation partialImportRep) {
|
||||||
|
if (partialImportRep.getRoles() == null) return null;
|
||||||
|
return partialImportRep.getRoles().getClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName(RoleRepresentation roleRep) {
|
||||||
|
if (roleRep.getName() == null)
|
||||||
|
throw new IllegalStateException("Client role to import does not have a name");
|
||||||
|
return roleRep.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCombinedName(String clientId, RoleRepresentation roleRep) {
|
||||||
|
return clientId + "-->" + getName(roleRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exists(RealmModel realm, KeycloakSession session, String clientId, RoleRepresentation roleRep) {
|
||||||
|
System.out.println("**** exists *****");
|
||||||
|
System.out.println("clientId =" + clientId);
|
||||||
|
ClientModel client = realm.getClientByClientId(clientId);
|
||||||
|
if (client == null) return false;
|
||||||
|
|
||||||
|
System.out.println("client=" + client);
|
||||||
|
for (RoleModel role : client.getRoles()) {
|
||||||
|
if (getName(roleRep).equals(role.getName())) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String existsMessage(String clientId, RoleRepresentation roleRep) {
|
||||||
|
return "Client role '" + getName(roleRep) + "' for client '" + clientId + "' already exists.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceType getResourceType() {
|
||||||
|
return ResourceType.CLIENT_ROLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void overwrite(RealmModel realm, KeycloakSession session, String clientId, RoleRepresentation roleRep) {
|
||||||
|
ClientModel client = realm.getClientByClientId(clientId);
|
||||||
|
checkForComposite(roleRep);
|
||||||
|
RoleModel role = client.getRole(getName(roleRep));
|
||||||
|
checkForOverwriteComposite(role);
|
||||||
|
RealmRolesPartialImport.RoleHelper helper = new RealmRolesPartialImport.RoleHelper(realm);
|
||||||
|
helper.updateRole(roleRep, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForComposite(RoleRepresentation roleRep) {
|
||||||
|
if (roleRep.isComposite()) {
|
||||||
|
throw new IllegalArgumentException("Composite role '" + getName(roleRep) + "' can not be partially imported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForOverwriteComposite(RoleModel role) {
|
||||||
|
if (role.isComposite()) {
|
||||||
|
throw new IllegalArgumentException("Composite role '" + role.getName() + "' can not be overwritten.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void create(RealmModel realm, KeycloakSession session, String clientId, RoleRepresentation roleRep) {
|
||||||
|
ClientModel client = realm.getClientByClientId(clientId);
|
||||||
|
if (client == null) {
|
||||||
|
throw new IllegalStateException("Client '" + clientId + "' does not exist for client role " + getName(roleRep));
|
||||||
|
}
|
||||||
|
checkForComposite(roleRep);
|
||||||
|
client.addRole(getName(roleRep));
|
||||||
|
overwrite(realm, session, clientId, roleRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void prepare(PartialImportRepresentation partialImportRep,
|
||||||
|
RealmModel realm,
|
||||||
|
KeycloakSession session,
|
||||||
|
Map<String, Set<RoleRepresentation>> resourcesToOverwrite,
|
||||||
|
Map<String, Set<RoleRepresentation>> resourcesToSkip) throws ErrorResponseException {
|
||||||
|
Map<String, List<RoleRepresentation>> repList = getRepList(partialImportRep);
|
||||||
|
for (String clientId : repList.keySet()) {
|
||||||
|
resourcesToOverwrite.put(clientId, new HashSet<RoleRepresentation>());
|
||||||
|
resourcesToSkip.put(clientId, new HashSet<RoleRepresentation>());
|
||||||
|
for (RoleRepresentation roleRep : repList.get(clientId)) {
|
||||||
|
if (exists(realm, session, clientId, roleRep)) {
|
||||||
|
switch (partialImportRep.getPolicy()) {
|
||||||
|
case SKIP:
|
||||||
|
resourcesToSkip.get(clientId).add(roleRep);
|
||||||
|
break;
|
||||||
|
case OVERWRITE:
|
||||||
|
resourcesToOverwrite.get(clientId).add(roleRep);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw exists(existsMessage(clientId, roleRep));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ErrorResponseException exists(String message) {
|
||||||
|
Response error = ErrorResponse.exists(message);
|
||||||
|
return new ErrorResponseException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PartialImportResult overwritten(String clientId, RoleRepresentation roleRep) {
|
||||||
|
return PartialImportResult.overwritten(getResourceType(), getCombinedName(clientId, roleRep), roleRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PartialImportResult skipped(String clientId, RoleRepresentation roleRep) {
|
||||||
|
return PartialImportResult.skipped(getResourceType(), getCombinedName(clientId, roleRep), roleRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PartialImportResult added(String clientId, RoleRepresentation roleRep) {
|
||||||
|
return PartialImportResult.added(getResourceType(), getCombinedName(clientId, roleRep), roleRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartialImportResults doImport(PartialImportRepresentation partialImportRep, RealmModel realm, KeycloakSession session) throws ErrorResponseException {
|
||||||
|
PartialImportResults results = new PartialImportResults();
|
||||||
|
Map<String, List<RoleRepresentation>> repList = getRepList(partialImportRep);
|
||||||
|
if ((repList == null) || repList.isEmpty()) return results;
|
||||||
|
|
||||||
|
final Map<String, Set<RoleRepresentation>> toOverwrite = new HashMap<>();
|
||||||
|
final Map<String, Set<RoleRepresentation>> toSkip = new HashMap<>();
|
||||||
|
prepare(partialImportRep, realm, session, toOverwrite, toSkip);
|
||||||
|
|
||||||
|
for (String clientId : toOverwrite.keySet()) {
|
||||||
|
for (RoleRepresentation roleRep : toOverwrite.get(clientId)) {
|
||||||
|
System.out.println("overwriting " + getResourceType() + " " + getCombinedName(clientId, roleRep));
|
||||||
|
try {
|
||||||
|
overwrite(realm, session, clientId, roleRep);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ErrorResponseException(ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
results.addResult(overwritten(clientId, roleRep));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String clientId : toSkip.keySet()) {
|
||||||
|
for (RoleRepresentation roleRep : toSkip.get(clientId)) {
|
||||||
|
System.out.println("skipping " + getResourceType() + " " + getCombinedName(clientId, roleRep));
|
||||||
|
results.addResult(skipped(clientId, roleRep));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String clientId : repList.keySet()) {
|
||||||
|
for (RoleRepresentation roleRep : repList.get(clientId)) {
|
||||||
|
if (toOverwrite.get(clientId).contains(roleRep)) continue;
|
||||||
|
if (toSkip.get(clientId).contains(roleRep)) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
System.out.println("adding " + getResourceType() + " " + getCombinedName(clientId, roleRep));
|
||||||
|
create(realm, session, clientId, roleRep);
|
||||||
|
results.addResult(added(clientId, roleRep));
|
||||||
|
} catch (Exception e) {
|
||||||
|
//e.printStackTrace();
|
||||||
|
throw new ErrorResponseException(ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
import org.keycloak.services.resources.admin.ClientResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class ClientsPartialImport extends AbstractPartialImport<ClientRepresentation> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ClientRepresentation> getRepList(PartialImportRepresentation partialImportRep) {
|
||||||
|
return partialImportRep.getClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName(ClientRepresentation clientRep) {
|
||||||
|
return clientRep.getClientId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(RealmModel realm, KeycloakSession session, ClientRepresentation clientRep) {
|
||||||
|
return realm.getClientByClientId(getName(clientRep)) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String existsMessage(ClientRepresentation clientRep) {
|
||||||
|
return "Client id '" + getName(clientRep) + "' already exists";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceType getResourceType() {
|
||||||
|
return ResourceType.CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void overwrite(RealmModel realm, KeycloakSession session, ClientRepresentation clientRep) {
|
||||||
|
ClientModel clientModel = realm.getClientByClientId(getName(clientRep));
|
||||||
|
ClientResource.updateClientFromRep(clientRep, clientModel, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void create(RealmModel realm, KeycloakSession session, ClientRepresentation clientRep) {
|
||||||
|
clientRep.setId(null);
|
||||||
|
RepresentationToModel.createClient(session, realm, clientRep, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class ErrorResponseException extends Exception {
|
||||||
|
private Response response;
|
||||||
|
|
||||||
|
public ErrorResponseException(Response response) {
|
||||||
|
this.response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
import org.keycloak.services.resources.admin.IdentityProviderResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class IdentityProvidersPartialImport extends AbstractPartialImport<IdentityProviderRepresentation> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IdentityProviderRepresentation> getRepList(PartialImportRepresentation partialImportRep) {
|
||||||
|
return partialImportRep.getIdentityProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName(IdentityProviderRepresentation idpRep) {
|
||||||
|
return idpRep.getAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(RealmModel realm, KeycloakSession session, IdentityProviderRepresentation idpRep) {
|
||||||
|
return realm.getIdentityProviderByAlias(getName(idpRep)) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String existsMessage(IdentityProviderRepresentation idpRep) {
|
||||||
|
return "Identity Provider '" + getName(idpRep) + "' already exists.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceType getResourceType() {
|
||||||
|
return ResourceType.IDP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void overwrite(RealmModel realm, KeycloakSession session, IdentityProviderRepresentation idpRep) {
|
||||||
|
IdentityProviderResource.updateIdpFromRep(idpRep, realm, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void create(RealmModel realm, KeycloakSession session, IdentityProviderRepresentation idpRep) {
|
||||||
|
IdentityProviderModel identityProvider = RepresentationToModel.toModel(realm, idpRep);
|
||||||
|
realm.addIdentityProvider(identityProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public interface PartialImport {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param rep
|
||||||
|
* @param realm
|
||||||
|
* @param session
|
||||||
|
* @return
|
||||||
|
* @throws ErrorResponseException if an error was detected trying to doImport a resource.
|
||||||
|
*/
|
||||||
|
public PartialImportResults doImport(PartialImportRepresentation rep, RealmModel realm, KeycloakSession session) throws ErrorResponseException;
|
||||||
|
}
|
|
@ -0,0 +1,338 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
import org.keycloak.services.resources.admin.AdminEventBuilder;
|
||||||
|
import org.keycloak.services.resources.admin.ClientResource;
|
||||||
|
import org.keycloak.services.resources.admin.IdentityProviderResource;
|
||||||
|
import org.keycloak.services.resources.admin.UsersResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class PartialImportManager {
|
||||||
|
private List<PartialImport> partialImports = new ArrayList<>();
|
||||||
|
|
||||||
|
private final PartialImportRepresentation rep;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final UriInfo uriInfo;
|
||||||
|
private final AdminEventBuilder adminEvent;
|
||||||
|
|
||||||
|
private final Set<UserRepresentation> usersToOverwrite = new HashSet<>();
|
||||||
|
private final Set<ClientRepresentation> clientsToOverwrite = new HashSet<>();
|
||||||
|
private final Set<IdentityProviderRepresentation> idpsToOverwrite = new HashSet<>();
|
||||||
|
|
||||||
|
private int added = 0;
|
||||||
|
private int skipped = 0;
|
||||||
|
private int overwritten = 0;
|
||||||
|
|
||||||
|
public PartialImportManager(PartialImportRepresentation rep, KeycloakSession session, RealmModel realm,
|
||||||
|
UriInfo uriInfo, AdminEventBuilder adminEvent) {
|
||||||
|
this.rep = rep;
|
||||||
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.uriInfo = uriInfo;
|
||||||
|
this.adminEvent = adminEvent;
|
||||||
|
|
||||||
|
partialImports.add(new UsersPartialImport());
|
||||||
|
partialImports.add(new ClientsPartialImport());
|
||||||
|
partialImports.add(new IdentityProvidersPartialImport());
|
||||||
|
partialImports.add(new RealmRolesPartialImport());
|
||||||
|
partialImports.add(new ClientRolesPartialImport());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response saveResources() {
|
||||||
|
|
||||||
|
PartialImportResults results = new PartialImportResults();
|
||||||
|
|
||||||
|
for (PartialImport partialImport : partialImports) {
|
||||||
|
try {
|
||||||
|
results.addAllResults(partialImport.doImport(rep, realm, session));
|
||||||
|
} catch (ErrorResponseException error) {
|
||||||
|
if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly();
|
||||||
|
return error.getResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PartialImportResult result : results.getResults()) {
|
||||||
|
switch (result.getAction()) {
|
||||||
|
case ADDED : addedEvent(result); break;
|
||||||
|
case OVERWRITTEN: overwrittenEvent(result); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.getTransaction().isActive()) {
|
||||||
|
session.getTransaction().commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok(results).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addedEvent(PartialImportResult result) {
|
||||||
|
adminEvent.operation(OperationType.CREATE)
|
||||||
|
.resourcePath(uriInfo)
|
||||||
|
.representation(result.getRepresentation())
|
||||||
|
.success();
|
||||||
|
};
|
||||||
|
|
||||||
|
private void overwrittenEvent(PartialImportResult result) {
|
||||||
|
adminEvent.operation(OperationType.UPDATE)
|
||||||
|
.resourcePath(uriInfo)
|
||||||
|
.representation(result.getRepresentation())
|
||||||
|
.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Response response = prepareForExistingResources();
|
||||||
|
if (response != null) return response;
|
||||||
|
|
||||||
|
response = saveUsers();
|
||||||
|
if (response != null) {
|
||||||
|
session.getTransaction().rollback();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = saveClients();
|
||||||
|
if (response != null) {
|
||||||
|
session.getTransaction().rollback();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = saveIdps();
|
||||||
|
if (response != null) {
|
||||||
|
session.getTransaction().rollback();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.getTransaction().isActive()) {
|
||||||
|
session.getTransaction().commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Response.ok(resultsMap()).build();*/
|
||||||
|
//}
|
||||||
|
/*
|
||||||
|
private Map resultsMap() {
|
||||||
|
Map<String, Integer> results = new HashMap<>();
|
||||||
|
results.put("added", added);
|
||||||
|
results.put("skipped", skipped);
|
||||||
|
results.put("overwritten", overwritten);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an error response or null
|
||||||
|
private Response prepareForExistingResources() {
|
||||||
|
|
||||||
|
if (rep.hasUsers()) {
|
||||||
|
Response response = prepareUsers();
|
||||||
|
if (response != null) return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rep.hasClients()) {
|
||||||
|
Response response = prepareClients();
|
||||||
|
if (response != null) return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rep.hasIdps()) {
|
||||||
|
Response response = prepareIdps();
|
||||||
|
if (response != null) return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an error response or null
|
||||||
|
private Response prepareClients() {
|
||||||
|
Set<ClientRepresentation> toSkip = new HashSet<>();
|
||||||
|
for (ClientRepresentation client : rep.getClients()) {
|
||||||
|
if (clientExists(client)) {
|
||||||
|
switch (rep.getPolicy()) {
|
||||||
|
case SKIP: toSkip.addResult(client); break;
|
||||||
|
case OVERWRITE: clientsToOverwrite.addResult(client); break;
|
||||||
|
default: return ErrorResponse.exists("Client id '" + client.getClientId() + "' already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ClientRepresentation client : toSkip) {
|
||||||
|
rep.getClients().remove(client);
|
||||||
|
skipped(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean clientExists(ClientRepresentation rep) {
|
||||||
|
return realm.getClientByClientId(rep.getClientId()) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an error response or null
|
||||||
|
private Response saveClients() {
|
||||||
|
if (!rep.hasClients()) return null;
|
||||||
|
|
||||||
|
for (ClientRepresentation client : clientsToOverwrite) {
|
||||||
|
ClientModel clientModel = realm.getClientByClientId(client.getClientId());
|
||||||
|
ClientResource.updateClientFromRep(client, clientModel, session);
|
||||||
|
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(client).success();
|
||||||
|
overwritten(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ClientRepresentation client : rep.getClients()) {
|
||||||
|
if (clientsToOverwrite.contains(client)) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
RepresentationToModel.createClient(session, realm, client, true);
|
||||||
|
added(client);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly();
|
||||||
|
return ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an error response or null
|
||||||
|
private Response prepareIdps() {
|
||||||
|
Set<IdentityProviderRepresentation> toSkip = new HashSet<>();
|
||||||
|
for (IdentityProviderRepresentation idp : rep.getIdentityProviders()) {
|
||||||
|
if (idpExists(idp)) {
|
||||||
|
switch (rep.getPolicy()) {
|
||||||
|
case SKIP: toSkip.addResult(idp); break;
|
||||||
|
case OVERWRITE: idpsToOverwrite.addResult(idp); break;
|
||||||
|
default: return ErrorResponse.exists("Identity Provider '" + idp.getAlias() + "' already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IdentityProviderRepresentation idp : toSkip) {
|
||||||
|
rep.getIdentityProviders().remove(idp);
|
||||||
|
skipped(idp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean idpExists(IdentityProviderRepresentation rep) {
|
||||||
|
return realm.getIdentityProviderByAlias(rep.getAlias()) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an error response or null
|
||||||
|
private Response saveIdps() {
|
||||||
|
if (!rep.hasIdps()) return null;
|
||||||
|
|
||||||
|
for (IdentityProviderRepresentation idp : idpsToOverwrite) {
|
||||||
|
IdentityProviderResource.updateIdpFromRep(idp, realm, session);
|
||||||
|
overwritten(idp);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (IdentityProviderRepresentation idp : rep.getIdentityProviders()) {
|
||||||
|
if (idpsToOverwrite.contains(idp)) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
IdentityProviderModel identityProvider = RepresentationToModel.toModel(idp);
|
||||||
|
realm.addIdentityProvider(identityProvider);
|
||||||
|
added(idp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly();
|
||||||
|
return ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an error response or null
|
||||||
|
private Response prepareUsers() {
|
||||||
|
Set<UserRepresentation> toSkip = new HashSet<>();
|
||||||
|
for (UserRepresentation user : rep.getUsers()) {
|
||||||
|
if (session.users().getUserByUsername(user.getUsername(), realm) != null) {
|
||||||
|
switch (rep.getPolicy()) {
|
||||||
|
case SKIP: toSkip.addResult(user); break;
|
||||||
|
case OVERWRITE: usersToOverwrite.addResult(user); break;
|
||||||
|
default: return ErrorResponse.exists("User '" + user.getUsername() + "' already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((user.getEmail() != null) && (session.users().getUserByEmail(user.getEmail(), realm) != null)) {
|
||||||
|
switch (rep.getPolicy()) {
|
||||||
|
case SKIP: toSkip.addResult(user); break;
|
||||||
|
case OVERWRITE: usersToOverwrite.addResult(user); break;
|
||||||
|
default: FAIL: return ErrorResponse.exists("User email '" + user.getEmail() + "' already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UserRepresentation user : toSkip) {
|
||||||
|
rep.getUsers().remove(user);
|
||||||
|
skipped(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns an error response or null
|
||||||
|
private Response saveUsers() {
|
||||||
|
if (!rep.hasUsers()) return null;
|
||||||
|
|
||||||
|
for (UserRepresentation user: usersToOverwrite) {
|
||||||
|
System.out.println("overwriting user " + user.getUsername());
|
||||||
|
UserModel userModel = session.users().getUserByUsername(user.getUsername(), realm);
|
||||||
|
UsersResource.updateUserFromRep(userModel, user, null, realm, session);
|
||||||
|
overwritten(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UserRepresentation user : rep.getUsers()) {
|
||||||
|
if (usersToOverwrite.contains(user)) continue;
|
||||||
|
try {
|
||||||
|
System.out.println("saving user " + user.getUsername());
|
||||||
|
Map<String, ClientModel> apps = realm.getClientNameMap();
|
||||||
|
UserModel userModel = RepresentationToModel.createUser(session, realm, user, apps);
|
||||||
|
added(user);
|
||||||
|
} catch (Exception e) {
|
||||||
|
//e.printStackTrace();
|
||||||
|
if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly();
|
||||||
|
return ErrorResponse.error(e.getMessage(), Response.Status.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class PartialImportResult {
|
||||||
|
|
||||||
|
private final Action action;
|
||||||
|
private final String resourceType;
|
||||||
|
private final String resourceName;
|
||||||
|
private final Object representation;
|
||||||
|
|
||||||
|
private PartialImportResult(Action action, ResourceType resourceType, String resourceName, Object representation) {
|
||||||
|
this.action = action;
|
||||||
|
this.resourceType = resourceType.toString();
|
||||||
|
this.resourceName = resourceName;
|
||||||
|
this.representation = representation;
|
||||||
|
};
|
||||||
|
|
||||||
|
public static PartialImportResult skipped(ResourceType resourceType, String resourceName, Object representation) {
|
||||||
|
return new PartialImportResult(Action.SKIPPED, resourceType, resourceName, representation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartialImportResult added(ResourceType resourceType, String resourceName, Object representation) {
|
||||||
|
return new PartialImportResult(Action.ADDED, resourceType, resourceName, representation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartialImportResult overwritten(ResourceType resourceType, String resourceName, Object representation) {
|
||||||
|
return new PartialImportResult(Action.OVERWRITTEN, resourceType, resourceName, representation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceName() {
|
||||||
|
return resourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public Object getRepresentation() {
|
||||||
|
return representation;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.services.resources.admin.AdminEventBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class PartialImportResults {
|
||||||
|
|
||||||
|
private final Set<PartialImportResult> importResults = new HashSet<>();
|
||||||
|
|
||||||
|
public void addResult(PartialImportResult result) {
|
||||||
|
System.out.println("PartialImportResults: add " + result.getResourceName() + " action=" + result.getAction());
|
||||||
|
importResults.add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAllResults(PartialImportResults results) {
|
||||||
|
importResults.addAll(results.getResults());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAdded() {
|
||||||
|
int added = 0;
|
||||||
|
for (PartialImportResult result : importResults) {
|
||||||
|
if (result.getAction() == Action.ADDED) added++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOverwritten() {
|
||||||
|
int overwritten = 0;
|
||||||
|
for (PartialImportResult result : importResults) {
|
||||||
|
System.out.println("action=" + result.getAction());
|
||||||
|
if (result.getAction() == Action.OVERWRITTEN) overwritten++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return overwritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSkipped() {
|
||||||
|
int skipped = 0;
|
||||||
|
for (PartialImportResult result : importResults) {
|
||||||
|
if (result.getAction() == Action.SKIPPED) skipped++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<PartialImportResult> getResults() {
|
||||||
|
return importResults;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
|
import org.keycloak.services.resources.admin.RoleResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class RealmRolesPartialImport extends AbstractPartialImport<RoleRepresentation> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RoleRepresentation> getRepList(PartialImportRepresentation partialImportRep) {
|
||||||
|
if (partialImportRep.getRoles() == null) return null;
|
||||||
|
return partialImportRep.getRoles().getRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName(RoleRepresentation roleRep) {
|
||||||
|
if (roleRep.getName() == null)
|
||||||
|
throw new IllegalStateException("Realm role to import does not have a name");
|
||||||
|
return roleRep.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(RealmModel realm, KeycloakSession session, RoleRepresentation roleRep) {
|
||||||
|
for (RoleModel role : realm.getRoles()) {
|
||||||
|
if (getName(roleRep).equals(role.getName())) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String existsMessage(RoleRepresentation roleRep) {
|
||||||
|
return "Realm role '" + getName(roleRep) + "' already exists.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceType getResourceType() {
|
||||||
|
return ResourceType.REALM_ROLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void overwrite(RealmModel realm, KeycloakSession session, RoleRepresentation roleRep) {
|
||||||
|
checkForComposite(roleRep);
|
||||||
|
RoleModel role = realm.getRole(getName(roleRep));
|
||||||
|
checkForOverwriteComposite(role);
|
||||||
|
RoleHelper helper = new RoleHelper(realm);
|
||||||
|
helper.updateRole(roleRep, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForComposite(RoleRepresentation roleRep) {
|
||||||
|
if (roleRep.isComposite()) {
|
||||||
|
throw new IllegalArgumentException("Composite role '" + getName(roleRep) + "' can not be partially imported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForOverwriteComposite(RoleModel role) {
|
||||||
|
if (role.isComposite()) {
|
||||||
|
throw new IllegalArgumentException("Composite role '" + role.getName() + "' can not be overwritten.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void create(RealmModel realm, KeycloakSession session, RoleRepresentation roleRep) {
|
||||||
|
checkForComposite(roleRep);
|
||||||
|
realm.addRole(getName(roleRep));
|
||||||
|
overwrite(realm, session, roleRep);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RoleHelper extends RoleResource {
|
||||||
|
public RoleHelper(RealmModel realm) {
|
||||||
|
super(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateRole(RoleRepresentation rep, RoleModel role) {
|
||||||
|
super.updateRole(rep, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author ssilvert
|
||||||
|
*/
|
||||||
|
public enum ResourceType {
|
||||||
|
USER, CLIENT, IDP, REALM_ROLE, CLIENT_ROLE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
switch(this) {
|
||||||
|
case USER: return "User";
|
||||||
|
case CLIENT: return "Client";
|
||||||
|
case IDP: return "Identity Provider";
|
||||||
|
case REALM_ROLE: return "Realm Role";
|
||||||
|
case CLIENT_ROLE: return "Client Role";
|
||||||
|
default: return super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @author tags. All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.partialimport;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.services.resources.admin.UsersResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
|
*/
|
||||||
|
public class UsersPartialImport extends AbstractPartialImport<UserRepresentation> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserRepresentation> getRepList(PartialImportRepresentation partialImportRep) {
|
||||||
|
return partialImportRep.getUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName(UserRepresentation user) {
|
||||||
|
return user.getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(RealmModel realm, KeycloakSession session, UserRepresentation user) {
|
||||||
|
return userNameExists(realm, session, user) || userEmailExists(realm, session, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean userNameExists(RealmModel realm, KeycloakSession session, UserRepresentation user) {
|
||||||
|
return session.users().getUserByUsername(user.getUsername(), realm) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean userEmailExists(RealmModel realm, KeycloakSession session, UserRepresentation user) {
|
||||||
|
return (user.getEmail() != null) &&
|
||||||
|
(session.users().getUserByEmail(user.getEmail(), realm) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String existsMessage(UserRepresentation user) {
|
||||||
|
if (user.getEmail() == null) {
|
||||||
|
return "User with user name " + getName(user) + " already exists.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "User with user name " + getName(user) + " or with email " + user.getEmail() + " already exists.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceType getResourceType() {
|
||||||
|
return ResourceType.USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void overwrite(RealmModel realm, KeycloakSession session, UserRepresentation user) {
|
||||||
|
UserModel userModel = session.users().getUserByUsername(user.getUsername(), realm);
|
||||||
|
UsersResource.updateUserFromRep(userModel, user, null, realm, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void create(RealmModel realm, KeycloakSession session, UserRepresentation user) {
|
||||||
|
Map<String, ClientModel> apps = realm.getClientNameMap();
|
||||||
|
user.setId(null);
|
||||||
|
RepresentationToModel.createUser(session, realm, user, apps);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -67,7 +67,7 @@ public class ClientResource {
|
||||||
private AdminEventBuilder adminEvent;
|
private AdminEventBuilder adminEvent;
|
||||||
protected ClientModel client;
|
protected ClientModel client;
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
protected UriInfo uriInfo;
|
protected UriInfo uriInfo;
|
||||||
|
|
||||||
|
@ -106,11 +106,7 @@ public class ClientResource {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (TRUE.equals(rep.isServiceAccountsEnabled()) && !client.isServiceAccountsEnabled()) {
|
updateClientFromRep(rep, client, session);
|
||||||
new ClientManager(new RealmManager(session)).enableServiceAccount(client);;
|
|
||||||
}
|
|
||||||
|
|
||||||
RepresentationToModel.updateClient(rep, client);
|
|
||||||
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
|
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
|
@ -118,6 +114,13 @@ public class ClientResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void updateClientFromRep(ClientRepresentation rep, ClientModel client, KeycloakSession session) throws ModelDuplicateException {
|
||||||
|
if (TRUE.equals(rep.isServiceAccountsEnabled()) && !client.isServiceAccountsEnabled()) {
|
||||||
|
new ClientManager(new RealmManager(session)).enableServiceAccount(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
RepresentationToModel.updateClient(rep, client);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get representation of the client
|
* Get representation of the client
|
||||||
|
@ -365,9 +368,9 @@ public class ClientResource {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
|
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
|
||||||
return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
|
return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get application session count
|
* Get application session count
|
||||||
*
|
*
|
||||||
|
|
|
@ -58,7 +58,7 @@ public class IdentityProviderResource {
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
private final IdentityProviderModel identityProviderModel;
|
private final IdentityProviderModel identityProviderModel;
|
||||||
private final AdminEventBuilder adminEvent;
|
private final AdminEventBuilder adminEvent;
|
||||||
|
|
||||||
@Context private UriInfo uriInfo;
|
@Context private UriInfo uriInfo;
|
||||||
|
|
||||||
public IdentityProviderResource(RealmAuth auth, RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel, AdminEventBuilder adminEvent) {
|
public IdentityProviderResource(RealmAuth auth, RealmModel realm, KeycloakSession session, IdentityProviderModel identityProviderModel, AdminEventBuilder adminEvent) {
|
||||||
|
@ -94,9 +94,9 @@ public class IdentityProviderResource {
|
||||||
this.auth.requireManage();
|
this.auth.requireManage();
|
||||||
|
|
||||||
this.realm.removeIdentityProviderByAlias(this.identityProviderModel.getAlias());
|
this.realm.removeIdentityProviderByAlias(this.identityProviderModel.getAlias());
|
||||||
|
|
||||||
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
|
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
|
||||||
|
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,30 +113,34 @@ public class IdentityProviderResource {
|
||||||
try {
|
try {
|
||||||
this.auth.requireManage();
|
this.auth.requireManage();
|
||||||
|
|
||||||
String internalId = providerRep.getInternalId();
|
updateIdpFromRep(providerRep, realm, session);
|
||||||
String newProviderId = providerRep.getAlias();
|
|
||||||
String oldProviderId = getProviderIdByInternalId(this.realm, internalId);
|
|
||||||
|
|
||||||
this.realm.updateIdentityProvider(RepresentationToModel.toModel(realm, providerRep));
|
|
||||||
|
|
||||||
if (oldProviderId != null && !oldProviderId.equals(newProviderId)) {
|
|
||||||
|
|
||||||
// Admin changed the ID (alias) of identity provider. We must update all clients and users
|
|
||||||
logger.debug("Changing providerId in all clients and linked users. oldProviderId=" + oldProviderId + ", newProviderId=" + newProviderId);
|
|
||||||
|
|
||||||
updateUsersAfterProviderAliasChange(this.session.users().getUsers(this.realm, false), oldProviderId, newProviderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(providerRep).success();
|
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(providerRep).success();
|
||||||
|
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
return ErrorResponse.exists("Identity Provider " + providerRep.getAlias() + " already exists");
|
return ErrorResponse.exists("Identity Provider " + providerRep.getAlias() + " already exists");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void updateIdpFromRep(IdentityProviderRepresentation providerRep, RealmModel realm, KeycloakSession session) {
|
||||||
|
String internalId = providerRep.getInternalId();
|
||||||
|
String newProviderId = providerRep.getAlias();
|
||||||
|
String oldProviderId = getProviderIdByInternalId(realm, internalId);
|
||||||
|
|
||||||
|
realm.updateIdentityProvider(RepresentationToModel.toModel(realm, providerRep));
|
||||||
|
|
||||||
|
if (oldProviderId != null && !oldProviderId.equals(newProviderId)) {
|
||||||
|
|
||||||
|
// Admin changed the ID (alias) of identity provider. We must update all clients and users
|
||||||
|
logger.debug("Changing providerId in all clients and linked users. oldProviderId=" + oldProviderId + ", newProviderId=" + newProviderId);
|
||||||
|
|
||||||
|
updateUsersAfterProviderAliasChange(session.users().getUsers(realm, false), oldProviderId, newProviderId, realm, session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// return ID of IdentityProvider from realm based on internalId of this provider
|
// return ID of IdentityProvider from realm based on internalId of this provider
|
||||||
private String getProviderIdByInternalId(RealmModel realm, String providerInternalId) {
|
private static String getProviderIdByInternalId(RealmModel realm, String providerInternalId) {
|
||||||
List<IdentityProviderModel> providerModels = realm.getIdentityProviders();
|
List<IdentityProviderModel> providerModels = realm.getIdentityProviders();
|
||||||
for (IdentityProviderModel providerModel : providerModels) {
|
for (IdentityProviderModel providerModel : providerModels) {
|
||||||
if (providerModel.getInternalId().equals(providerInternalId)) {
|
if (providerModel.getInternalId().equals(providerInternalId)) {
|
||||||
|
@ -147,17 +151,17 @@ public class IdentityProviderResource {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUsersAfterProviderAliasChange(List<UserModel> users, String oldProviderId, String newProviderId) {
|
private static void updateUsersAfterProviderAliasChange(List<UserModel> users, String oldProviderId, String newProviderId, RealmModel realm, KeycloakSession session) {
|
||||||
for (UserModel user : users) {
|
for (UserModel user : users) {
|
||||||
FederatedIdentityModel federatedIdentity = this.session.users().getFederatedIdentity(user, oldProviderId, this.realm);
|
FederatedIdentityModel federatedIdentity = session.users().getFederatedIdentity(user, oldProviderId, realm);
|
||||||
if (federatedIdentity != null) {
|
if (federatedIdentity != null) {
|
||||||
// Remove old link first
|
// Remove old link first
|
||||||
this.session.users().removeFederatedIdentity(this.realm, user, oldProviderId);
|
session.users().removeFederatedIdentity(realm, user, oldProviderId);
|
||||||
|
|
||||||
// And create new
|
// And create new
|
||||||
FederatedIdentityModel newFederatedIdentity = new FederatedIdentityModel(newProviderId, federatedIdentity.getUserId(), federatedIdentity.getUserName(),
|
FederatedIdentityModel newFederatedIdentity = new FederatedIdentityModel(newProviderId, federatedIdentity.getUserId(), federatedIdentity.getUserName(),
|
||||||
federatedIdentity.getToken());
|
federatedIdentity.getToken());
|
||||||
this.session.users().addFederatedIdentity(this.realm, user, newFederatedIdentity);
|
session.users().addFederatedIdentity(realm, user, newFederatedIdentity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,10 +267,10 @@ public class IdentityProviderResource {
|
||||||
auth.requireManage();
|
auth.requireManage();
|
||||||
IdentityProviderMapperModel model = RepresentationToModel.toModel(mapper);
|
IdentityProviderMapperModel model = RepresentationToModel.toModel(mapper);
|
||||||
model = realm.addIdentityProviderMapper(model);
|
model = realm.addIdentityProviderMapper(model);
|
||||||
|
|
||||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId())
|
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId())
|
||||||
.representation(mapper).success();
|
.representation(mapper).success();
|
||||||
|
|
||||||
return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
|
return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.PatternSyntaxException;
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
import org.keycloak.partialimport.PartialImportManager;
|
||||||
|
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base resource class for the admin REST api of one realm
|
* Base resource class for the admin REST api of one realm
|
||||||
|
@ -241,7 +243,7 @@ public class RealmAdminResource {
|
||||||
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
for (final UserFederationProviderModel fedProvider : federationProviders) {
|
||||||
usersSyncManager.refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId());
|
usersSyncManager.refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
adminEvent.operation(OperationType.UPDATE).representation(rep).success();
|
adminEvent.operation(OperationType.UPDATE).representation(rep).success();
|
||||||
return Response.noContent().build();
|
return Response.noContent().build();
|
||||||
} catch (PatternSyntaxException e) {
|
} catch (PatternSyntaxException e) {
|
||||||
|
@ -466,7 +468,7 @@ public class RealmAdminResource {
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
query.user(user);
|
query.user(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dateFrom != null) {
|
if(dateFrom != null) {
|
||||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
Date from = null;
|
Date from = null;
|
||||||
|
@ -477,7 +479,7 @@ public class RealmAdminResource {
|
||||||
}
|
}
|
||||||
query.fromDate(from);
|
query.fromDate(from);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dateTo != null) {
|
if(dateTo != null) {
|
||||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
Date to = null;
|
Date to = null;
|
||||||
|
@ -501,7 +503,7 @@ public class RealmAdminResource {
|
||||||
|
|
||||||
return query.getResultList();
|
return query.getResultList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get admin events
|
* Get admin events
|
||||||
*
|
*
|
||||||
|
@ -540,15 +542,15 @@ public class RealmAdminResource {
|
||||||
if (authClient != null) {
|
if (authClient != null) {
|
||||||
query.authClient(authClient);
|
query.authClient(authClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authUser != null) {
|
if (authUser != null) {
|
||||||
query.authUser(authUser);
|
query.authUser(authUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authIpAddress != null) {
|
if (authIpAddress != null) {
|
||||||
query.authIpAddress(authIpAddress);
|
query.authIpAddress(authIpAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourcePath != null) {
|
if (resourcePath != null) {
|
||||||
query.resourcePath(resourcePath);
|
query.resourcePath(resourcePath);
|
||||||
}
|
}
|
||||||
|
@ -561,7 +563,7 @@ public class RealmAdminResource {
|
||||||
}
|
}
|
||||||
query.operation(t);
|
query.operation(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dateFrom != null) {
|
if(dateFrom != null) {
|
||||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
Date from = null;
|
Date from = null;
|
||||||
|
@ -572,7 +574,7 @@ public class RealmAdminResource {
|
||||||
}
|
}
|
||||||
query.fromTime(from);
|
query.fromTime(from);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(dateTo != null) {
|
if(dateTo != null) {
|
||||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
Date to = null;
|
Date to = null;
|
||||||
|
@ -606,7 +608,7 @@ public class RealmAdminResource {
|
||||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||||
eventStore.clear(realm.getId());
|
eventStore.clear(realm.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete all admin events
|
* Delete all admin events
|
||||||
*
|
*
|
||||||
|
@ -709,5 +711,19 @@ public class RealmAdminResource {
|
||||||
return ModelToRepresentation.toGroupHierarchy(found, true);
|
return ModelToRepresentation.toGroupHierarchy(found, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partial import from a JSON file to an existing realm.
|
||||||
|
*
|
||||||
|
* @param uriInfo
|
||||||
|
* @param rep
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Path("partialImport")
|
||||||
|
@POST
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public Response partialImport(final @Context UriInfo uriInfo, PartialImportRepresentation rep) {
|
||||||
|
auth.requireManage();
|
||||||
|
PartialImportManager partialImport = new PartialImportManager(rep, session, realm, uriInfo, adminEvent);
|
||||||
|
return partialImport.saveResources();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,7 +150,7 @@ public class UsersResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserFromRep(user, rep, attrsToRemove);
|
updateUserFromRep(user, rep, attrsToRemove, realm, session);
|
||||||
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
|
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
|
||||||
|
|
||||||
if (session.getTransaction().isActive()) {
|
if (session.getTransaction().isActive()) {
|
||||||
|
@ -189,7 +189,7 @@ public class UsersResource {
|
||||||
try {
|
try {
|
||||||
UserModel user = session.users().addUser(realm, rep.getUsername());
|
UserModel user = session.users().addUser(realm, rep.getUsername());
|
||||||
Set<String> emptySet = Collections.emptySet();
|
Set<String> emptySet = Collections.emptySet();
|
||||||
updateUserFromRep(user, rep, emptySet);
|
updateUserFromRep(user, rep, emptySet, realm, session);
|
||||||
|
|
||||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success();
|
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success();
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ public class UsersResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUserFromRep(UserModel user, UserRepresentation rep, Set<String> attrsToRemove) {
|
public static void updateUserFromRep(UserModel user, UserRepresentation rep, Set<String> attrsToRemove, RealmModel realm, KeycloakSession session) {
|
||||||
if (realm.isEditUsernameAllowed()) {
|
if (realm.isEditUsernameAllowed()) {
|
||||||
user.setUsername(rep.getUsername());
|
user.setUsername(rep.getUsername());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue