package org.gcube.oidc.keycloak.d4science; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; 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.List; import java.util.Map; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.io.IOUtils; import org.gcube.oidc.D4ScienceMappings.Role; import org.gcube.oidc.D4ScienceMappings.Scope; import org.gcube.oidc.keycloak.KeycloakHelper; import org.gcube.oidc.keycloak.KeycloakResourceCreationException; 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.RoleRepresentation; 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; public class ClientsCreatorFromExport { private static final boolean DELETE_CLIENTS = false; private static final boolean CREATE_CLIENTS = false; private static final boolean MAP_ROLES = true; private static final boolean DL_AVATARS = false; private static final boolean SHOW_STATS = true; private KeycloakHelper kh; private Keycloak keycloak; private ExportParser exportParser; private String realm; // public Map 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 : getExportParser().getAllContexts()) { System.out.println("adding client: " + contextClient); ClientResource client = kh.addClient(realmResource, contextClient, contextClient, contextClient + "'s context", ""); Map 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 scopeMap = new HashMap<>(); for (Scope scopeToAdd : Scope.values()) { ScopeRepresentation scope = new ScopeRepresentation(scopeToAdd.asString()); scopeMap.put(scopeToAdd, scope); } Set 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)); } // Mapping group (from LDAP mapping) to relatives client's Member role String ldapGroupPath = contextClient.substring(1); System.out.println("\tMapping '" + ldapGroupPath + "' LDAP group to client's 'Member' role"); kh.mapGroupToCLientRole(kh.findGroupByPath(realmResource, ldapGroupPath), client, roleMap.get(Role.MEMBER)); } } 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 getRoleResourceScopes(String resourceName, Role role) { // TODO Implement when/if needed return Collections.emptySet(); } protected void configureClientResource(ClientResource client, Map roleMap, ResourceRepresentation resource) throws KeycloakResourceCreationException { String resourceName = resource.getName(); Set policies = new HashSet<>(); for (Role role : getInvolvedRoles(resourceName)) { Map> 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 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 : getExportParser().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>> usersToContextsAndRoles = getExportParser().getAllUserContextsAndRoles(); for (String userName : usersToContextsAndRoles.keySet()) { System.out.println("- user: " + userName); UserResource userResource = kh.findUser(realmResource, userName); if (userResource != null) { Map> 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) { List oldRoles = userResource.roles().clientLevel(clientResource.toRepresentation().getId()).listAll(); RoleRepresentation memberRole = null; for (RoleRepresentation roleRepresentation : oldRoles) { if (roleRepresentation.getName().equals(Role.MEMBER.asString())) { memberRole = roleRepresentation; } } oldRoles.remove(memberRole); if (oldRoles.size() > 0) { System.out.println("\t\tremoving old roles [" + oldRoles + "]"); userResource.roles().clientLevel(clientResource.toRepresentation().getId()).remove(oldRoles); } // This is no more needed, it is assigned automatically since is member // of the corresponding LDAP group // 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); if (Role.exists(role)) { kh.mapRoleTo(userResource, clientResource, role); } else { System.err.println("Found not standard role: " + 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 void saveAvatarsLocally(String avatarBaseURL, String destinationFolder) { URL baseURL; try { baseURL = new URL(avatarBaseURL); } catch (MalformedURLException e) { System.err.println("Bad avatar base URL provided: " + e.getMessage()); return; } Map usersAndAvatars = getExportParser().getAllUsersAndAvatars(); File destinationFolderFile = null; if (destinationFolder != null) { destinationFolderFile = new File(destinationFolder); } else { destinationFolderFile = new File("."); } if (!destinationFolderFile.exists()) { System.out.println("- Creating destination folder: " + destinationFolderFile.getAbsolutePath()); destinationFolderFile.mkdir(); } else if (!destinationFolderFile.isDirectory()) { System.err.println("Provided destination is not a folder: " + destinationFolderFile.getAbsolutePath()); } for (String username : usersAndAvatars.keySet()) { String avatarURLSuffix = usersAndAvatars.get(username); if (avatarURLSuffix != null) { System.out.println("- Saving avatar of user: " + username); try { URL avatarURL = new URL(baseURL, avatarURLSuffix); InputStream avatarIS = avatarURL.openStream(); File destinationFile = new File(destinationFolder, username); FileOutputStream destinationFOS = new FileOutputStream(destinationFile); IOUtils.copy(avatarIS, destinationFOS); destinationFOS.close(); if (destinationFile.exists() && destinationFile.length() == 0) { System.out.println(" -- Deleting zero bytes length avatar for user: " + username); destinationFile.delete(); } } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("--- Avatar URL not provided for user: " + username); } } } public ExportParser getExportParser() { return exportParser; } public static void main(String[] args) throws Exception { String kcServerURL = null; String username = null; String password = null; String realm = null; FileInputStream exportFileFIS = null; String avatarBaseURL = null; String avatarExportFolder = null; if (args.length < 5) { System.err.println("Missing params.\n\nUsage: " + ClientsCreatorFromExport.class.getName() + " [keycloak_serverURL] [username] [password] [realm] [export_file] [[avatar_base_url] [avatar_export_folder]]"); return; } else { kcServerURL = args[0]; username = args[1]; password = args[2]; realm = args[3]; try { exportFileFIS = new FileInputStream(args[4]); } catch (FileNotFoundException e) { System.err.println("Export file not found: " + args[4]); return; } if (args.length > 5) { avatarBaseURL = args[5]; } if (args.length == 7) { avatarExportFolder = args[6]; } } ClientsCreatorFromExport creator = new ClientsCreatorFromExport(kcServerURL, username, password, realm, exportFileFIS); Date start = new Date(); Date lap; System.out.println("Start at " + start); if (DELETE_CLIENTS) { System.out.println("Deleting clients..."); creator.deleteClients(); lap = new Date(); System.out.println("[lap seconds: " + new Long(lap.getTime() - start.getTime()).floatValue() / 1000 + "]"); } if (CREATE_CLIENTS) { System.out.println("\n\n * * * Creating clients * * *"); creator.createClients(); lap = new Date(); System.out.println("[lap seconds: " + new Long(lap.getTime() - start.getTime()).floatValue() / 1000 + "]"); } if (MAP_ROLES) { System.out.println("\n\n * * * Mapping users to client's roles * * *"); creator.mapUsersWithRolesToClients(); lap = new Date(); System.out.println("[lap seconds: " + new Long(lap.getTime() - start.getTime()).floatValue() / 1000 + "]"); } if (DL_AVATARS && avatarBaseURL != null) { System.out.println("\n\n * * * Exporting user's avatar * * *"); creator.saveAvatarsLocally(avatarBaseURL, avatarExportFolder); } Date end = new Date(); System.out.println("Total elapsed seconds: " + new Long(end.getTime() - start.getTime()).floatValue() / 1000); if (SHOW_STATS) { System.out.println("\nClients: " + creator.getExportParser().getAllContexts().size()); System.out.println("Users: " + creator.getExportParser().getAllUsersAndAvatars().size()); Map>> ucar = creator.getExportParser().getAllUserContextsAndRoles(); float totalUsersRoles = 0; for (String user : ucar.keySet()) { for (String context : ucar.get(user).keySet()) { totalUsersRoles += ucar.get(user).get(context).size() + 1; } } System.out.println( "Roles per user: " + totalUsersRoles / creator.getExportParser().getAllUsersAndAvatars().size()); } } }