316 lines
14 KiB
Java
316 lines
14 KiB
Java
|
package com.nubisware.oidc.keycloak.gcube;
|
||
|
|
||
|
import java.io.FileInputStream;
|
||
|
import java.io.IOException;
|
||
|
import java.io.UnsupportedEncodingException;
|
||
|
import java.security.KeyManagementException;
|
||
|
import java.security.NoSuchAlgorithmException;
|
||
|
import java.util.Collections;
|
||
|
import java.util.Date;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.HashSet;
|
||
|
import java.util.Iterator;
|
||
|
import java.util.Map;
|
||
|
import java.util.Set;
|
||
|
import java.util.TreeMap;
|
||
|
import java.util.TreeSet;
|
||
|
|
||
|
import javax.xml.parsers.ParserConfigurationException;
|
||
|
|
||
|
import org.keycloak.admin.client.Keycloak;
|
||
|
import org.keycloak.admin.client.resource.ClientResource;
|
||
|
import org.keycloak.admin.client.resource.PolicyResource;
|
||
|
import org.keycloak.admin.client.resource.RealmResource;
|
||
|
import org.keycloak.admin.client.resource.ResourceResource;
|
||
|
import org.keycloak.admin.client.resource.RoleResource;
|
||
|
import org.keycloak.admin.client.resource.UserResource;
|
||
|
import org.keycloak.representations.idm.authorization.DecisionStrategy;
|
||
|
import org.keycloak.representations.idm.authorization.Logic;
|
||
|
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||
|
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||
|
import org.xml.sax.SAXException;
|
||
|
|
||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||
|
import com.nubisware.oidc.gcube.D4ScienceMappings.Role;
|
||
|
import com.nubisware.oidc.gcube.D4ScienceMappings.Scope;
|
||
|
import com.nubisware.oidc.keycloak.KeycloakHelper;
|
||
|
import com.nubisware.oidc.keycloak.KeycloakResourceCreationException;
|
||
|
|
||
|
public class ClientsCreatorFromExport {
|
||
|
|
||
|
private KeycloakHelper kh;
|
||
|
private Keycloak keycloak;
|
||
|
private ExportParser exportParser;
|
||
|
private String realm;
|
||
|
// public Map<Role, Scope> role2Scope;
|
||
|
|
||
|
public ClientsCreatorFromExport(String keycloakURL, String adminUsername, String adminPassword, String realm,
|
||
|
FileInputStream exportFileFIS) throws SAXException, IOException, ParserConfigurationException,
|
||
|
KeyManagementException, NoSuchAlgorithmException {
|
||
|
|
||
|
this.exportParser = new ExportParser(exportFileFIS);
|
||
|
this.kh = KeycloakHelper.getInstance(keycloakURL);
|
||
|
this.keycloak = kh.newKeycloakAdmin(adminUsername, adminPassword);
|
||
|
this.realm = realm;
|
||
|
// role2Scope = new TreeMap<>();
|
||
|
// role2Scope.put(Role.ACCOUNTING_MANAGER, Scope.BELONGS);
|
||
|
}
|
||
|
|
||
|
public void createClients() throws KeycloakResourceCreationException, UnsupportedEncodingException {
|
||
|
RealmResource realmResource = keycloak.realm(realm);
|
||
|
for (String contextClient : exportParser.getAllContexts()) {
|
||
|
System.out.println("adding client: " + contextClient);
|
||
|
ClientResource client = kh.addClient(realmResource, contextClient,
|
||
|
contextClient,
|
||
|
contextClient + "'s context", "");
|
||
|
Map<Role, RoleResource> roleMap = new HashMap<>();
|
||
|
for (Role roleToAdd : Role.values()) {
|
||
|
System.out.println("\tcreating role: " + roleToAdd);
|
||
|
RoleResource role = kh.addRole(client, true, roleToAdd.asString(), roleToAdd.asString(),
|
||
|
roleToAdd.asString() + " role", null);
|
||
|
|
||
|
roleMap.put(roleToAdd, role);
|
||
|
}
|
||
|
Map<Scope, ScopeRepresentation> scopeMap = new HashMap<>();
|
||
|
for (Scope scopeToAdd : Scope.values()) {
|
||
|
ScopeRepresentation scope = new ScopeRepresentation(scopeToAdd.asString());
|
||
|
scopeMap.put(scopeToAdd, scope);
|
||
|
}
|
||
|
Set<ScopeRepresentation> resourceScopes = new HashSet<>(scopeMap.values());
|
||
|
|
||
|
String[] resources = getClientResources(contextClient);
|
||
|
if (resources.length > 0) {
|
||
|
for (String resourceToAdd : resources) {
|
||
|
System.out.println("\t\tadding resource: " + resourceToAdd);
|
||
|
// TODO Set also the resource type
|
||
|
String type = "urn:" + client.toRepresentation().getClientId() + ":resources:service";
|
||
|
ResourceResource resource = kh.addResource(client, resourceToAdd, type, resourceToAdd, false,
|
||
|
resourceScopes,
|
||
|
null);
|
||
|
|
||
|
configureClientResource(client, roleMap, resource.toRepresentation());
|
||
|
}
|
||
|
} else {
|
||
|
configureClientResource(client, roleMap, client.authorization().resources().resources().get(0));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private String[] getClientResources(String contextClient) {
|
||
|
// TODO Implement when/if needed
|
||
|
return new String[] {};
|
||
|
}
|
||
|
|
||
|
private Role[] getInvolvedRoles(String resourceName) {
|
||
|
// TODO Implement when/if needed
|
||
|
return Role.values();
|
||
|
}
|
||
|
|
||
|
private Set<String> getRoleResourceScopes(String resourceName, Role role) {
|
||
|
// TODO Implement when/if needed
|
||
|
return Collections.emptySet();
|
||
|
}
|
||
|
|
||
|
protected void configureClientResource(ClientResource client, Map<Role, RoleResource> roleMap,
|
||
|
ResourceRepresentation resource) throws KeycloakResourceCreationException {
|
||
|
|
||
|
String resourceName = resource.getName();
|
||
|
Set<String> policies = new HashSet<>();
|
||
|
for (Role role : getInvolvedRoles(resourceName)) {
|
||
|
Map<String, Set<String>> policyClientRoles = new HashMap<>();
|
||
|
policyClientRoles.put(client.toRepresentation().getClientId(),
|
||
|
Collections.singleton(roleMap.get(role).toRepresentation().getName()));
|
||
|
|
||
|
System.out.println("\t\t\tadding role resource policy for role: " + role);
|
||
|
Set<String> roleResourceScopes = getRoleResourceScopes(resourceName, role);
|
||
|
PolicyResource newPR = kh.addRoleResourcePolicy(client, Collections.singleton(resourceName),
|
||
|
roleResourceScopes, role.asString() + "_policy", Logic.POSITIVE,
|
||
|
policyClientRoles);
|
||
|
|
||
|
policies.add(newPR.toRepresentation().getName());
|
||
|
}
|
||
|
System.out.println(
|
||
|
"\t\t\tdeleting default js policy that is no more needed");
|
||
|
// This will also the delete default strategy
|
||
|
|
||
|
client.authorization().policies().policy(client.authorization().policies().findByName("Default Policy").getId())
|
||
|
.remove();
|
||
|
|
||
|
System.out.println(
|
||
|
"\t\t\tcreating new permission for role policies with affirmative strategy");
|
||
|
|
||
|
kh.addResourcePermission(client, Collections.singleton(resourceName), "Default Permission",
|
||
|
DecisionStrategy.AFFIRMATIVE, policies);
|
||
|
|
||
|
System.out.println("\t\t\tupdating the default permission on server");
|
||
|
}
|
||
|
|
||
|
public void deleteClients() {
|
||
|
RealmResource realmResource = keycloak.realm(realm);
|
||
|
for (String contextClient : exportParser.getAllContexts()) {
|
||
|
System.out.println("- deleting: " + contextClient);
|
||
|
try {
|
||
|
kh.removeClient(realmResource, contextClient);
|
||
|
} catch (UnsupportedEncodingException e) {
|
||
|
e.printStackTrace();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void mapUsersWithRolesToClients() throws UnsupportedEncodingException {
|
||
|
RealmResource realmResource = keycloak.realm(realm);
|
||
|
Map<String, Map<String, Set<String>>> usersToContextsAndRoles = exportParser.getAllUserContextsAndRoles();
|
||
|
for (String userName : usersToContextsAndRoles.keySet()) {
|
||
|
System.out.println("- user: " + userName);
|
||
|
UserResource userResource = kh.findUser(realmResource, userName);
|
||
|
if (userResource != null) {
|
||
|
Map<String, Set<String>> userContextsAndRoles = usersToContextsAndRoles.get(userName);
|
||
|
for (String userContext : userContextsAndRoles.keySet()) {
|
||
|
String clientId = userContext;
|
||
|
System.out.println("\tcontext: " + userContext);
|
||
|
ClientResource clientResource = kh.findClient(realmResource, clientId);
|
||
|
if (clientResource != null) {
|
||
|
System.out.println("\t\tmapping default role: " + Role.MEMBER.asString());
|
||
|
kh.mapRoleTo(userResource, clientResource, Role.MEMBER.asString());
|
||
|
for (String role : userContextsAndRoles.get(userContext)) {
|
||
|
System.out.println("\t\tmapping role: " + role);
|
||
|
kh.mapRoleTo(userResource, clientResource, role);
|
||
|
}
|
||
|
} else {
|
||
|
System.err.println("Client not found on keycloak: " + userContext);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
System.err.println("User not found on keycloak: " + userName);
|
||
|
}
|
||
|
System.out.println();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public ExportParser getExportParser() {
|
||
|
return exportParser;
|
||
|
}
|
||
|
|
||
|
public class ExportParser {
|
||
|
|
||
|
private ArrayNode rootNode;
|
||
|
|
||
|
public ExportParser(FileInputStream exportFileFIS)
|
||
|
throws SAXException, IOException, ParserConfigurationException {
|
||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||
|
rootNode = (ArrayNode) objectMapper.readTree(exportFileFIS);
|
||
|
}
|
||
|
|
||
|
public Set<String> getAllUsers() {
|
||
|
Set<String> users = new TreeSet<>();
|
||
|
Iterator<JsonNode> arrayIterator = rootNode.elements();
|
||
|
while (arrayIterator.hasNext()) {
|
||
|
JsonNode entry = arrayIterator.next();
|
||
|
users.add(entry.get("username").asText());
|
||
|
}
|
||
|
return users;
|
||
|
}
|
||
|
|
||
|
public Set<String> getAllContexts() {
|
||
|
Set<String> distinctContexts = new TreeSet<>();
|
||
|
Iterator<JsonNode> arrayIterator = rootNode.elements();
|
||
|
while (arrayIterator.hasNext()) {
|
||
|
JsonNode entry = arrayIterator.next();
|
||
|
ObjectNode contextsNode = (ObjectNode) entry.get("contexts");
|
||
|
contextsNode.fieldNames().forEachRemaining(f -> distinctContexts.add(f));
|
||
|
}
|
||
|
return distinctContexts;
|
||
|
}
|
||
|
|
||
|
public Map<String, Set<String>> getContextsAndRoles(String user) {
|
||
|
Iterator<JsonNode> arrayIterator = rootNode.elements();
|
||
|
while (arrayIterator.hasNext()) {
|
||
|
JsonNode entry = arrayIterator.next();
|
||
|
String username = entry.get("username").asText();
|
||
|
if (!user.equals(username)) {
|
||
|
continue;
|
||
|
}
|
||
|
Map<String, Set<String>> contextAndRoles = new TreeMap<>();
|
||
|
ObjectNode contextsNode = (ObjectNode) entry.get("contexts");
|
||
|
Iterator<String> contextIterator = contextsNode.fieldNames();
|
||
|
while (contextIterator.hasNext()) {
|
||
|
String context = (String) contextIterator.next();
|
||
|
Set<String> roles = new TreeSet<>();
|
||
|
ArrayNode rolesNodes = (ArrayNode) contextsNode.get(context);
|
||
|
rolesNodes.elements().forEachRemaining(r -> roles.add(r.asText()));
|
||
|
contextAndRoles.put(context, roles);
|
||
|
}
|
||
|
return contextAndRoles;
|
||
|
}
|
||
|
return Collections.emptyMap();
|
||
|
}
|
||
|
|
||
|
public Map<String, Map<String, Set<String>>> getAllUserContextsAndRoles() {
|
||
|
Map<String, Map<String, Set<String>>> usersToContextAndRoles = new TreeMap<>();
|
||
|
Iterator<JsonNode> arrayIterator = rootNode.elements();
|
||
|
while (arrayIterator.hasNext()) {
|
||
|
JsonNode entry = arrayIterator.next();
|
||
|
String username = entry.get("username").asText();
|
||
|
Map<String, Set<String>> contextAndRoles = new TreeMap<>();
|
||
|
ObjectNode contextsNode = (ObjectNode) entry.get("contexts");
|
||
|
Iterator<String> contextIterator = contextsNode.fieldNames();
|
||
|
while (contextIterator.hasNext()) {
|
||
|
String context = (String) contextIterator.next();
|
||
|
Set<String> roles = new TreeSet<>();
|
||
|
ArrayNode rolesNodes = (ArrayNode) contextsNode.get(context);
|
||
|
rolesNodes.elements().forEachRemaining(r -> roles.add(r.asText()));
|
||
|
contextAndRoles.put(context, roles);
|
||
|
}
|
||
|
usersToContextAndRoles.put(username, contextAndRoles);
|
||
|
}
|
||
|
return usersToContextAndRoles;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static void main(String[] args) throws Exception {
|
||
|
String serverURL = null;
|
||
|
String username = null;
|
||
|
String password = null;
|
||
|
String realm = null;
|
||
|
FileInputStream exportFileFIS = null;
|
||
|
if (args.length < 5) {
|
||
|
System.err.println("Missing params.\n\nUsage: " + ClientsCreatorFromExport.class.getName()
|
||
|
+ " [serverURL] [username] [password] [realm] [export_file]");
|
||
|
|
||
|
return;
|
||
|
} else {
|
||
|
serverURL = args[0];
|
||
|
username = args[1];
|
||
|
password = args[2];
|
||
|
realm = args[3];
|
||
|
exportFileFIS = new FileInputStream(args[4]);
|
||
|
}
|
||
|
ClientsCreatorFromExport creator = new ClientsCreatorFromExport(serverURL, username, password, realm,
|
||
|
exportFileFIS);
|
||
|
|
||
|
Date start = new Date();
|
||
|
System.out.println("Start at " + start);
|
||
|
System.out.println("Deleting clients...");
|
||
|
creator.deleteClients();
|
||
|
System.out.println("\n\n * * * Creating clients * * *");
|
||
|
creator.createClients();
|
||
|
System.out.println("\n\n * * * Mapping users to client's roles * * *");
|
||
|
creator.mapUsersWithRolesToClients();
|
||
|
Date end = new Date();
|
||
|
System.out.println("Elapsed seconds: " + new Long(end.getTime() - start.getTime()).floatValue() / 1000);
|
||
|
System.out.println("\nClients: " + creator.getExportParser().getAllContexts().size());
|
||
|
System.out.println("Users: " + creator.getExportParser().getAllUsers().size());
|
||
|
Map<String, Map<String, Set<String>>> ucar = creator.getExportParser().getAllUserContextsAndRoles();
|
||
|
float rolesPerUserMean = 0;
|
||
|
for (String user : ucar.keySet()) {
|
||
|
for (String context : ucar.get(user).keySet()) {
|
||
|
rolesPerUserMean += ucar.get(user).get(context).size() + 1;
|
||
|
}
|
||
|
}
|
||
|
System.out.println("Roles per user mean: " + rolesPerUserMean / creator.getExportParser().getAllUsers().size());
|
||
|
}
|
||
|
}
|