oidc-keycloak-library/src/main/java/org/gcube/oidc/keycloak/d4science/ClientsCreatorFromExport.java

347 lines
16 KiB
Java

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<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 : getExportParser().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));
}
// 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<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 : 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<String, Map<String, Set<String>>> usersToContextsAndRoles = getExportParser().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) {
List<RoleRepresentation> 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<String, String> 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<String, Map<String, Set<String>>> 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());
}
}
}