blob: 378da0a735c9afe6a3380a412b5197848ad851df [file] [log] [blame]
/**
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.airavata;
import org.apache.airavata.api.Airavata;
import org.apache.airavata.api.client.AiravataClientFactory;
import org.apache.airavata.common.exception.ApplicationSettingsException;
import org.apache.airavata.common.utils.Constants;
import org.apache.airavata.credential.store.client.CredentialStoreClientFactory;
import org.apache.airavata.credential.store.cpi.CredentialStoreService;
import org.apache.airavata.model.appcatalog.gatewayprofile.GatewayResourceProfile;
import org.apache.airavata.model.credential.store.PasswordCredential;
import org.apache.airavata.model.error.AiravataClientException;
import org.apache.airavata.model.security.AuthzToken;
import org.apache.airavata.model.user.Status;
import org.apache.airavata.model.user.UserProfile;
import org.apache.airavata.model.workspace.Gateway;
import org.apache.airavata.model.workspace.GatewayApprovalStatus;
import org.apache.airavata.service.profile.client.ProfileServiceClientFactory;
import org.apache.airavata.service.profile.iam.admin.services.cpi.IamAdminServices;
import org.apache.airavata.service.profile.iam.admin.services.cpi.exception.IamAdminServicesException;
import org.apache.airavata.service.profile.tenant.cpi.TenantProfileService;
import org.apache.airavata.service.profile.tenant.cpi.exception.TenantProfileServiceException;
import org.apache.airavata.service.profile.user.cpi.UserProfileService;
import org.apache.thrift.TException;
import org.wso2.carbon.um.ws.api.stub.ClaimValue;
import org.wso2.carbon.um.ws.api.stub.RemoteUserStoreManagerServiceStub;
import org.wso2.carbon.um.ws.api.stub.RemoteUserStoreManagerServiceUserStoreExceptionException;
import java.io.FileInputStream;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.*;
import java.util.stream.Collectors;
public class MigrationManager {
private ArrayList<Wso2ISLoginCredentialsDAO> adminCredentials = new ArrayList<Wso2ISLoginCredentialsDAO>();
private static AuthzToken authzToken = new AuthzToken("empty_token");
// Default values
private String profileServiceServerHost = "localhost";
private int profileServiceServerPort = 8962;
private String airavataServiceServerHost = "localhost";
private int airavataServiceServerPort = 8930;
private boolean airavataServiceSecure = false;
private Map<String,String> roleConversionMap = createDefaultRoleConversionMap();
private String gatewayId = "gateway-id";
private String wso2ISAdminUsername = "username";
private String wso2ISAdminPassword = "password";
private String keycloakServiceURL = "https://iam.scigap.org/auth";
private String keycloakAdminUsername = "username";
private String keycloakAdminPassword = "password";
private String keycloakTrustStorePath = "../../modules/configuration/server/src/main/resources/client_truststore.jks";
private String keycloakTrustStorePassword = "password";
private String keycloakTemporaryUserPassword = "tempPassword";
// For some gateways in the legacy gateways table, the following information is missing and needs to be provided
private String gatewayURL = "http://localhost";
private String gatewayAdminUsername = "admin";
private String gatewayAdminFirstName = "Admin";
private String gatewayAdminLastName = "User";
private String gatewayAdminEmailAddress = "sgg@iu.edu";
// Names of properties in user-profile-migration.properties.template
private final static String GATEWAY_ID = "gateway-id";
private final static String GATEWAY_URL = "gateway.url";
private final static String GATEWAY_ADMIN_USERNAME = "gateway.admin.username";
private final static String GATEWAY_ADMIN_FIRST_NAME = "gateway.admin.first.name";
private final static String GATEWAY_ADMIN_LAST_NAME = "gateway.admin.last.name";
private final static String GATEWAY_ADMIN_EMAIL_ADDRESS = "gateway.admin.email.address";
private final static String WSO2IS_ADMIN_USERNAME = "wso2is.admin.username";
private final static String WSO2IS_ADMIN_PASSWORD = "wso2is.admin.password";
private final static String WSO2IS_ADMIN_ROLENAME = "wso2is.admin.rolename";
private final static String WSO2IS_ADMIN_READ_ONLY_ROLENAME = "wso2is.admin-read-only.rolename";
private final static String WSO2IS_GATEWAY_USER_ROLENAME = "wso2is.gateway-user.rolename";
private final static String WSO2IS_USER_PENDING_ROLENAME = "wso2is.user-pending.rolename";
private final static String WSO2IS_GATEWAY_PROVIDER_ROLENAME = "wso2is.gateway-provider.rolename";
private final static String AIRAVATA_SERVICE_HOST = "airavata.service.host";
private final static String AIRAVATA_SERVICE_PORT = "airavata.service.port";
private final static String AIRAVATA_SERVICE_SECURE = "airavata.service.secure";
private final static String PROFILE_SERVICE_HOST = "profile.service.host";
private final static String PROFILE_SERVICE_PORT = "profile.service.port";
private final static String KEYCLOAK_ADMIN_USERNAME = "keycloak.admin.username";
private final static String KEYCLOAK_ADMIN_PASSWORD = "keycloak.admin.password";
private final static String KEYCLOAK_SERVICE_URL = "keycloak.service-url";
private final static String KEYCLOAK_TRUSTSTORE_PATH = "keycloak.truststore.path";
private final static String KEYCLOAK_TRUSTSTORE_PASSWORD = "keycloak.truststore.password";
private final static String KEYCLOAK_USER_TEMP_PASSWORD = "keycloak.user.temp.password";
private Map<String,String> createDefaultRoleConversionMap() {
Map<String,String> roleConversionMap = new HashMap<>();
roleConversionMap.put("admin", "admin");
roleConversionMap.put("admin-read-only", "admin-read-only");
roleConversionMap.put("gateway-user", "gateway-user");
roleConversionMap.put("user-pending", "user-pending");
roleConversionMap.put("gateway-provider", "gateway-provider");
return roleConversionMap;
}
/*Add the credentials for all the tenants from which the profile should be migrated to Airavata DB*/
public void setISLoginCredentials(){
adminCredentials.add(new Wso2ISLoginCredentialsDAO(this.gatewayId, this.wso2ISAdminUsername, this.wso2ISAdminPassword));
// new credential records here...
}
/* Method used to fetch all the user profiles from the registered tenants */
public List<UserProfileDAO> getUserProfilesFromWso2IS(){
ArrayList<UserProfileDAO> userProfileList = new ArrayList<UserProfileDAO>();
for(Wso2ISLoginCredentialsDAO creds:adminCredentials){
RemoteUserStoreManagerServiceStub isClient = Wso2IdentityServerClient.getAdminServiceClient(creds.getLoginUserName(),creds.getLoginPassword(),"RemoteUserStoreManagerService");
String[] userList;
System.out.println("Fetching User Profiles for " + creds.getGateway() + " tenant ...");
try {
userList = isClient.getUserList("http://wso2.org/claims/givenname", "*", "default");
System.out.println("FirstName\tLastName\tEmail\t\t\tuserName\tCountry\tOrganization\tphone\tRoles");
String[] claims = {"http://wso2.org/claims/givenname",
"http://wso2.org/claims/lastname",
"http://wso2.org/claims/emailaddress",
"http://wso2.org/claims/country",
"http://wso2.org/claims/organization",
"http://wso2.org/claims/mobile",
"http://wso2.org/claims/telephone",
"http://wso2.org/claims/streetaddress",
"http://wso2.org/claims/role",
"http://wso2.org/claims/identity/accountLocked"};
for (String user : userList) {
UserProfileDAO userProfile = new UserProfileDAO();
ClaimValue[] retrievedClaimValues = isClient.getUserClaimValuesForClaims(user, claims, null);
List<String> phones = new ArrayList<String>();
for(ClaimValue claim:retrievedClaimValues){
if(claim.getClaimURI().equals(claims[0])){
userProfile.setFirstName(claim.getValue());
}else if(claim.getClaimURI().equals(claims[1])){
userProfile.setLastName(claim.getValue());
}else if(claim.getClaimURI().equals(claims[2])){
userProfile.setEmail(claim.getValue());
}else if(claim.getClaimURI().equals(claims[3])){
userProfile.setCountry(claim.getValue());
}else if(claim.getClaimURI().equals(claims[4])){
userProfile.setOrganization(claim.getValue());
}else if(claim.getClaimURI().equals(claims[5]) || claim.getClaimURI().equals(claims[6])){
phones.add(claim.getValue());
} else if(claim.getClaimURI().equals(claims[7])){
userProfile.setAddress(claim.getValue());
} else if(claim.getClaimURI().equals(claims[8])){
userProfile.setRoles(convertCommaSeparatedRolesToList(claim.getValue()));
} else if(claim.getClaimURI().equals(claims[9])){
userProfile.setAccountLocked(claim.getValue().equals("true"));
}
}
// Lowercase all usernames as required by Keycloak and User Profile service
userProfile.setUserName(user.toLowerCase());
userProfile.setGatewayID(creds.getGateway());
userProfile.setPhones(phones);
if (!userProfile.isAccountLocked()) {
System.out.println(userProfile.getFirstName() + "\t" + userProfile.getLastName() + "\t" + userProfile.getUserName() + "\t" + userProfile.getEmail() + "\t" + userProfile.getCountry() + "\t" + userProfile.getOrganization() + "\t" + userProfile.getAddress() + "\t" + userProfile.getRoles());
userProfileList.add(userProfile);
} else {
System.out.println("Skipping locked account for user " + user + "!");
}
}
} catch (RemoteException e) {
System.out.println(e.getMessage());
System.out.println(e.getCause());
e.printStackTrace();
} catch (RemoteUserStoreManagerServiceUserStoreExceptionException e) {
System.out.println(e.getMessage());
System.out.println(e.getCause());
e.printStackTrace();
}
}
System.out.println("User profiles from all the tenant are retrieved ...");
return userProfileList;
}
private List<String> convertCommaSeparatedRolesToList(String roles) {
return Arrays.stream(roles.split(","))
.filter(s -> !"Internal/everyone".equals(s))
.filter(s -> !"Internal/identity".equals(s))
.collect(Collectors.toList());
}
private TenantProfileService.Client getTenantProfileServiceClient() throws TenantProfileServiceException {
return ProfileServiceClientFactory.createTenantProfileServiceClient(profileServiceServerHost, profileServiceServerPort);
}
private Airavata.Client getAiravataClient() throws AiravataClientException {
return AiravataClientFactory.createAiravataClient(airavataServiceServerHost, airavataServiceServerPort);
}
private Airavata.Client getAiravataSecureClient() throws AiravataClientException {
return AiravataClientFactory.createAiravataSecureClient(airavataServiceServerHost, airavataServiceServerPort, keycloakTrustStorePath, keycloakTrustStorePassword, 10000);
}
private IamAdminServices.Client getIamAdminServicesClient() throws IamAdminServicesException {
return ProfileServiceClientFactory.createIamAdminServiceClient(profileServiceServerHost, profileServiceServerPort);
}
private PasswordCredential getPasswordCredential() {
PasswordCredential passwordCredential = new PasswordCredential();
passwordCredential.setGatewayId("dummy");
passwordCredential.setPortalUserName("dummy");
passwordCredential.setLoginUserName(keycloakAdminUsername);
passwordCredential.setPassword(keycloakAdminPassword);
return passwordCredential;
}
private boolean migrateGatewayProfileToAiravata() throws TException {
TenantProfileService.Client tenantProfileServiceClient = getTenantProfileServiceClient();
Airavata.Client airavataClient = airavataServiceSecure ? getAiravataSecureClient() : getAiravataClient();
IamAdminServices.Client iamAdminServicesClient = getIamAdminServicesClient();
// Get Gateway from Airavata API
Gateway gateway = airavataClient.getGateway(authzToken, gatewayId);
if (!GatewayApprovalStatus.APPROVED.equals(gateway.getGatewayApprovalStatus())) {
throw new RuntimeException("Gateway " + gatewayId + " is not APPROVED! Status is " + gateway.getGatewayApprovalStatus());
}
// Add Gateway through TenantProfileService
if (!tenantProfileServiceClient.isGatewayExist(authzToken, gatewayId)) {
System.out.println("Gateway [" + gatewayId + "] doesn't exist, adding in Profile Service...");
String airavataInternalGatewayId = tenantProfileServiceClient.addGateway(authzToken, gateway);
gateway.setAiravataInternalGatewayId(airavataInternalGatewayId);
} else {
System.out.println("Gateway [" + gatewayId + "] already exists in Profile Service");
gateway = tenantProfileServiceClient.getGateway(authzToken, gatewayId);
}
// Gateway URL is required by IAM Admin Services
if (gateway.getGatewayURL() == null) {
gateway.setGatewayURL(this.gatewayURL);
}
// Following are also required by IAM Admin Services in order to create an admin user for the realm
if (gateway.getIdentityServerUserName() == null) {
gateway.setIdentityServerUserName(this.gatewayAdminUsername);
}
if (gateway.getGatewayAdminFirstName() == null) {
gateway.setGatewayAdminFirstName(this.gatewayAdminFirstName);
}
if (gateway.getGatewayAdminLastName() == null) {
gateway.setGatewayAdminLastName(this.gatewayAdminLastName);
}
if (gateway.getGatewayAdminEmail() == null) {
gateway.setGatewayAdminEmail(this.gatewayAdminEmailAddress);
}
// Add Keycloak Tenant for Gateway
System.out.println("Creating Keycloak Tenant for gateway ...");
Gateway gatewayWithIdAndSecret = iamAdminServicesClient.setUpGateway(authzToken, gateway);
// Update Gateway profile with the client id and secret
System.out.println("Updating gateway with OAuth client id and secret ...");
tenantProfileServiceClient.updateGateway(authzToken, gatewayWithIdAndSecret);
KeycloakIdentityServerClient keycloakIdentityServerClient = getKeycloakIdentityServerClient();
// Set the admin user's password to the same as it was for wso2IS
keycloakIdentityServerClient.setUserPassword(gatewayId, this.gatewayAdminUsername, this.wso2ISAdminPassword);
// Create password credential for admin username and password
String passwordToken = airavataClient.registerPwdCredential(authzToken, gatewayId, this.gatewayAdminUsername, this.gatewayAdminUsername, this.wso2ISAdminPassword, "Keycloak admin password for realm " + gatewayId);
// Update gateway resource profile with tenant id (gatewayId) and admin user password token
GatewayResourceProfile gatewayResourceProfile = airavataClient.getGatewayResourceProfile(authzToken, gatewayId);
gatewayResourceProfile.setIdentityServerTenant(gatewayId);
gatewayResourceProfile.setIdentityServerPwdCredToken(passwordToken);
airavataClient.updateGatewayResourceProfile(authzToken, gatewayId, gatewayResourceProfile);
return true;
}
/* Method used to migrate User profiles to Airavata DB by making a call to User profile thrift Service */
private boolean migrateUserProfilesToAiravata(List<UserProfileDAO> ISProfileList) throws TException, ApplicationSettingsException {
System.out.println("Initiating migration to Airavata internal DB ...");
UserProfileService.Client client = ProfileServiceClientFactory.createUserProfileServiceClient(profileServiceServerHost, profileServiceServerPort);
UserProfile airavataUserProfile = new UserProfile();
// Here are the data associations...
for(UserProfileDAO ISProfile : ISProfileList){
airavataUserProfile.setAiravataInternalUserId(ISProfile.getUserName() + "@" + ISProfile.getGatewayID());
airavataUserProfile.setFirstName(ISProfile.getFirstName());
airavataUserProfile.setLastName(ISProfile.getLastName());
airavataUserProfile.setUserId(ISProfile.getUserName());
airavataUserProfile.setGatewayId(ISProfile.getGatewayID());
List<String> emails = new ArrayList<String>();
emails.add(ISProfile.getEmail());
airavataUserProfile.setEmails(emails);
airavataUserProfile.setHomeOrganization(ISProfile.getOrganization());
airavataUserProfile.setPhones(ISProfile.getPhones());
airavataUserProfile.setCountry(ISProfile.getCountry());
airavataUserProfile.setCreationTime(new Date().getTime());
airavataUserProfile.setLastAccessTime(new Date().getTime());
airavataUserProfile.setValidUntil(-1);
airavataUserProfile.setState(Status.ACTIVE);
//TODO: fix authtzToken, for now we are using empty token, but need to properly populate claims map
AuthzToken authzToken = new AuthzToken("dummy_token");
Map<String,String> claimsMap = new HashMap<>();
claimsMap.put(Constants.USER_NAME, ISProfile.getUserName());
claimsMap.put(Constants.GATEWAY_ID, ISProfile.getGatewayID());
authzToken.setClaimsMap(claimsMap);
client.addUserProfile(authzToken, airavataUserProfile);
}
return false;
}
private void migrateUserProfilesToKeycloak(List<UserProfileDAO> Wso2ISProfileList){
KeycloakIdentityServerClient client = getKeycloakIdentityServerClient();
client.migrateUserStore(Wso2ISProfileList, this.gatewayId, this.keycloakTemporaryUserPassword, this.roleConversionMap);
}
private KeycloakIdentityServerClient getKeycloakIdentityServerClient() {
return new KeycloakIdentityServerClient(this.keycloakServiceURL,
this.keycloakAdminUsername,
this.keycloakAdminPassword,
this.keycloakTrustStorePath,
this.keycloakTrustStorePassword);
}
private void loadConfigFile(String filename) {
Properties properties = new Properties();
try {
properties.load(new FileInputStream(filename));
// Load values from properties if they exist, otherwise will just use default values
this.gatewayId = properties.getProperty(GATEWAY_ID, this.gatewayId);
this.gatewayURL = properties.getProperty(GATEWAY_URL, this.gatewayURL);
this.gatewayAdminUsername = properties.getProperty(GATEWAY_ADMIN_USERNAME, this.gatewayAdminUsername);
this.gatewayAdminFirstName = properties.getProperty(GATEWAY_ADMIN_FIRST_NAME, this.gatewayAdminFirstName);
this.gatewayAdminLastName = properties.getProperty(GATEWAY_ADMIN_LAST_NAME, this.gatewayAdminLastName);
this.gatewayAdminEmailAddress = properties.getProperty(GATEWAY_ADMIN_EMAIL_ADDRESS, this.gatewayAdminEmailAddress);
this.wso2ISAdminUsername = properties.getProperty(WSO2IS_ADMIN_USERNAME, this.wso2ISAdminUsername);
this.wso2ISAdminPassword = properties.getProperty(WSO2IS_ADMIN_PASSWORD, this.wso2ISAdminPassword);
this.airavataServiceServerHost = properties.getProperty(AIRAVATA_SERVICE_HOST, this.airavataServiceServerHost);
this.airavataServiceServerPort = Integer.valueOf(properties.getProperty(AIRAVATA_SERVICE_PORT, Integer.toString(this.airavataServiceServerPort)));
this.airavataServiceSecure = Boolean.valueOf(properties.getProperty(AIRAVATA_SERVICE_SECURE, "false"));
this.profileServiceServerHost = properties.getProperty(PROFILE_SERVICE_HOST, this.profileServiceServerHost);
this.profileServiceServerPort = Integer.valueOf(properties.getProperty(PROFILE_SERVICE_PORT, Integer.toString(this.profileServiceServerPort)));
this.keycloakServiceURL = properties.getProperty(KEYCLOAK_SERVICE_URL, this.keycloakServiceURL);
this.keycloakAdminUsername = properties.getProperty(KEYCLOAK_ADMIN_USERNAME, this.keycloakAdminUsername);
this.keycloakAdminPassword = properties.getProperty(KEYCLOAK_ADMIN_PASSWORD, this.keycloakAdminPassword);
this.keycloakTrustStorePath = properties.getProperty(KEYCLOAK_TRUSTSTORE_PATH, this.keycloakTrustStorePath);
this.keycloakTrustStorePassword = properties.getProperty(KEYCLOAK_TRUSTSTORE_PASSWORD, this.keycloakTrustStorePassword);
this.keycloakTemporaryUserPassword = properties.getProperty(KEYCLOAK_USER_TEMP_PASSWORD, this.keycloakTemporaryUserPassword);
// Custom role names
this.roleConversionMap.put(properties.getProperty(WSO2IS_ADMIN_ROLENAME, "admin"), "admin");
this.roleConversionMap.put(properties.getProperty(WSO2IS_ADMIN_READ_ONLY_ROLENAME, "admin-read-only"), "admin-read-only");
this.roleConversionMap.put(properties.getProperty(WSO2IS_GATEWAY_USER_ROLENAME, "gateway-user"), "gateway-user");
this.roleConversionMap.put(properties.getProperty(WSO2IS_USER_PENDING_ROLENAME, "user-pending"), "user-pending");
this.roleConversionMap.put(properties.getProperty(WSO2IS_GATEWAY_PROVIDER_ROLENAME, "gateway-provider"), "gateway-provider");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
MigrationManager migrationManager = new MigrationManager();
if (args.length > 0) {
String configFilename = args[0];
migrationManager.loadConfigFile(configFilename);
}
migrationManager.setISLoginCredentials();
List<UserProfileDAO> userProfileList = migrationManager.getUserProfilesFromWso2IS();
try {
migrationManager.migrateGatewayProfileToAiravata();
// Must migrate profiles to Keycloak first because Profile Service will attempt to keep user profiles
// in since with Keycloak user profiles
migrationManager.migrateUserProfilesToKeycloak(userProfileList);
migrationManager.migrateUserProfilesToAiravata(userProfileList);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}