<commit_before>/*
 * 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.ranger.ldapusersync.process;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.PrivilegedAction;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.Subject;
import javax.ws.rs.core.MediaType;

import org.apache.hadoop.security.SecureClientLogin;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.ranger.unixusersync.config.UserGroupSyncConfig;
import org.apache.ranger.unixusersync.model.GroupUserInfo;
import org.apache.ranger.unixusersync.model.MUserInfo;
import org.apache.ranger.unixusersync.model.UserGroupInfo;
import org.apache.ranger.unixusersync.model.XGroupInfo;
import org.apache.ranger.unixusersync.model.XUserGroupInfo;
import org.apache.ranger.unixusersync.model.XUserInfo;
import org.apache.ranger.usergroupsync.UserGroupSink;
import org.apache.ranger.usersync.util.UserSyncUtil;

import com.google.common.collect.Table;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.jersey.client.urlconnection.HTTPSProperties;

public class LdapPolicyMgrUserGroupBuilder implements UserGroupSink {

private static final Logger LOG = Logger.getLogger(LdapPolicyMgrUserGroupBuilder.class);
	
	private static final String AUTHENTICATION_TYPE = "hadoop.security.authentication";	
	private String AUTH_KERBEROS = "kerberos";
	private static final String PRINCIPAL = "ranger.usersync.kerberos.principal";
	private static final String KEYTAB = "ranger.usersync.kerberos.keytab";
	private static final String NAME_RULE = "hadoop.security.auth_to_local";
	
	public static final String PM_USER_LIST_URI  = "/service/xusers/users/";				// GET
	private static final String PM_ADD_USER_GROUP_INFO_URI = "/service/xusers/users/userinfo";	// POST
	
	private static final String PM_ADD_GROUP_USER_INFO_URI = "/service/xusers/groups/groupinfo";	// POST
	
	public static final String PM_GROUP_LIST_URI = "/service/xusers/groups/";				// GET
	private static final String PM_ADD_GROUP_URI = "/service/xusers/groups/";				// POST
	
	private static final String PM_DEL_USER_GROUP_LINK_URI = "/service/xusers/group/${groupName}/user/${userName}"; // DELETE
	
	public static final String PM_USER_GROUP_MAP_LIST_URI = "/service/xusers/groupusers/";		// GET
	
	public static final String PM_GET_GROUP_USER_MAP_LIST_URI = "/service/xusers/groupusers/groupName/${groupName}";		// GET
	
	private static final String PM_ADD_LOGIN_USER_URI = "/service/users/default";			// POST
	private static final String GROUP_SOURCE_EXTERNAL ="1";
	private static String LOCAL_HOSTNAME = "unknown";
	private boolean isMockRun = false;
	private String policyMgrBaseUrl;
	
	private UserGroupSyncConfig  config = UserGroupSyncConfig.getInstance();

	private UserGroupInfo				usergroupInfo = new UserGroupInfo();
	private GroupUserInfo				groupuserInfo = new GroupUserInfo();
	
	Table<String, String, String> groupsUsersTable;
	
	private String keyStoreFile =  null;
	private String keyStoreFilepwd = null;
	private String trustStoreFile = null;
	private String trustStoreFilepwd = null;
	private String keyStoreType = null;
	private String trustStoreType = null;
	private HostnameVerifier hv =  null;

	private SSLContext sslContext = null;
	private String authenticationType = null;
	String principal;
	String keytab;
	String nameRules;
	
	static {
		try {
			LOCAL_HOSTNAME = java.net.InetAddress.getLocalHost().getCanonicalHostName();
		} catch (UnknownHostException e) {
			LOCAL_HOSTNAME = "unknown";
		}
	}
	
	synchronized public void init() throws Throwable {
		policyMgrBaseUrl = config.getPolicyManagerBaseURL();
		isMockRun = config.isMockRunEnabled();
		
		if (isMockRun) {
			LOG.setLevel(Level.DEBUG);
		}
		
		keyStoreFile =  config.getSSLKeyStorePath();
		keyStoreFilepwd = config.getSSLKeyStorePathPassword();
		trustStoreFile = config.getSSLTrustStorePath();
		trustStoreFilepwd = config.getSSLTrustStorePathPassword();
		keyStoreType = KeyStore.getDefaultType();
		trustStoreType = KeyStore.getDefaultType();
		authenticationType = config.getProperty(AUTHENTICATION_TYPE,"simple");
		try {
			principal = SecureClientLogin.getPrincipal(config.getProperty(PRINCIPAL,""), LOCAL_HOSTNAME);
		} catch (IOException ignored) {
			 // do nothing
		}
		keytab = config.getProperty(KEYTAB,"");
		nameRules = config.getProperty(NAME_RULE,"DEFAULT");

	}

	@Override
	public void addOrUpdateUser(String userName, List<String> groups) throws Throwable {
		//* Add user to groups mapping in the x_user table. 
		//* Here the assumption is that the user already exists in x_portal_user table.
		if ( ! isMockRun ) {
			// If the rest call to ranger admin fails, 
			// propagate the failure to the caller for retry in next sync cycle.
			if (addUserGroupInfo(userName, groups) == null ) {
				String msg = "Failed to add addorUpdate user group info";
				LOG.error(msg);
				throw new Exception(msg);
			}
		}

	}

	@Override
	public void addOrUpdateGroup(String groupName) throws Throwable {
		//* Build the group info object and do the rest call
			if ( ! isMockRun ) {
				if ( addGroupInfo(groupName) == null) {
					String msg = "Failed to add addorUpdate group info";
					LOG.error(msg);
					throw new Exception(msg);
				}
			}

	}
	
	private XGroupInfo addGroupInfo(final String groupName){
		XGroupInfo ret = null;
		XGroupInfo group = null;
		
		LOG.debug("INFO: addPMXAGroup(" + groupName + ")" );
		if (! isMockRun) {
			group = addXGroupInfo(groupName);
		}
		if (authenticationType != null && AUTH_KERBEROS.equalsIgnoreCase(authenticationType) && SecureClientLogin.isKerberosCredentialExists(principal,keytab)) {
			try {
				LOG.info("Using principal = " + principal + " and keytab = " + keytab);
				Subject sub = SecureClientLogin.loginUserFromKeytab(principal, keytab, nameRules);
				final XGroupInfo groupInfo = group;
				ret = Subject.doAs(sub, new PrivilegedAction<XGroupInfo>() {
					@Override
					public XGroupInfo run() {
						try {
							return getAddedGroupInfo(groupInfo);
						} catch (Exception e) {
							LOG.error("Failed to build Group List : ", e);
						}
						return null;
					}
				});
				return ret;
			} catch (Exception e) {
				LOG.error("Failed to Authenticate Using given Principal and Keytab : ", e);
			}
			return null;
		} else {
			return getAddedGroupInfo(group);
		}	
	}
	
	private XGroupInfo addXGroupInfo(String aGroupName) {
		
		XGroupInfo addGroup = new XGroupInfo();
		
		addGroup.setName(aGroupName);
		
		addGroup.setDescription(aGroupName + " - add from Unix box");
		
		addGroup.setGroupType("1");

		addGroup.setGroupSource(GROUP_SOURCE_EXTERNAL);
		groupuserInfo.setXgroupInfo(addGroup);

		return addGroup;
	}

	private XGroupInfo getAddedGroupInfo(XGroupInfo group){	
		XGroupInfo ret = null;
		
		Client c = getClient();
		
		WebResource r = c.resource(getURL(PM_ADD_GROUP_URI));
		
		Gson gson = new GsonBuilder().create();
		
		String jsonString = gson.toJson(group);
		
		LOG.debug("Group" + jsonString);
		
		String response = r.accept(MediaType.APPLICATION_JSON_TYPE).type(MediaType.APPLICATION_JSON_TYPE).post(String.class, jsonString);
		
		LOG.debug("RESPONSE: [" + response + "]");
		
		ret = gson.fromJson(response, XGroupInfo.class);
		
		return ret;	
	}

	public static void main(String[] args) throws Throwable {
		LdapPolicyMgrUserGroupBuilder  ugbuilder = new LdapPolicyMgrUserGroupBuilder();
		ugbuilder.init();

	}

	@Override
	public void addOrUpdateUser(String userName) throws Throwable {
		// First add to x_portal_user
		LOG.debug("INFO: addPMAccount(" + userName + ")" );
		if (! isMockRun) {
			if (addMUser(userName) == null) {
		        String msg = "Failed to add portal user";
		        LOG.error(msg);
		        throw new Exception(msg);
			}
		}
		List<String> groups = new ArrayList<String>();
		//* Build the user group info object with empty groups and do the rest call
		if ( ! isMockRun ) {
			// If the rest call to ranger admin fails, 
			// propagate the failure to the caller for retry in next sync cycle.
			if (addUserGroupInfo(userName, groups) == null ) {
				String msg = "Failed to add addorUpdate user group info";
				LOG.error(msg);
				throw new Exception(msg);
			}
		}
		
	}
	
	private UserGroupInfo addUserGroupInfo(String userName, List<String> groups){
		if(LOG.isDebugEnabled()) {
	 		LOG.debug("==> LdapPolicyMgrUserGroupBuilder.addUserGroupInfo " + userName + " and groups");
	 	}
		UserGroupInfo ret = null;
		XUserInfo user = null;
		LOG.debug("INFO: addPMXAUser(" + userName + ")" );
		
		if (! isMockRun) {
			user = addXUserInfo(userName);
		}
		for(String g : groups) {
				LOG.debug("INFO: addPMXAGroupToUser(" + userName + "," + g + ")" );
		}
		if (! isMockRun ) {
			addXUserGroupInfo(user, groups);
		}
		if (authenticationType != null && AUTH_KERBEROS.equalsIgnoreCase(authenticationType) && SecureClientLogin.isKerberosCredentialExists(principal, keytab)){
			try {
				Subject sub = SecureClientLogin.loginUserFromKeytab(principal, keytab, nameRules);
				final UserGroupInfo result = ret;
				ret = Subject.doAs(sub, new PrivilegedAction<UserGroupInfo>() {
					@Override
					public UserGroupInfo run() {
						try {
							return getUsergroupInfo(result);
						} catch (Exception e) {
							LOG.error("Failed to add User Group Info : ", e);
						}
						return null;
					}
				});
				return ret;
			} catch (Exception e) {
				LOG.error("Failed to Authenticate Using given Principal and Keytab : " , e);
			}
			return null;
		}else{
			return getUsergroupInfo(ret);
		}
	}
	
	private XUserInfo addXUserInfo(String aUserName) {
		
		XUserInfo xuserInfo = new XUserInfo();

		xuserInfo.setName(aUserName);
		
		xuserInfo.setDescription(aUserName + " - add from Unix box");
	   	
		usergroupInfo.setXuserInfo(xuserInfo);
		
		return xuserInfo;
	}

	private void addXUserGroupInfo(XUserInfo aUserInfo, List<String> aGroupList) {
		
		List<XGroupInfo> xGroupInfoList = new ArrayList<XGroupInfo>();
		
		for(String groupName : aGroupList) {
			XGroupInfo group = addXGroupInfo(groupName);
			xGroupInfoList.add(group);
			addXUserGroupInfo(aUserInfo, group);
		}
		
		usergroupInfo.setXgroupInfo(xGroupInfoList);
	}
	
	private XUserGroupInfo addXUserGroupInfo(XUserInfo aUserInfo, XGroupInfo aGroupInfo) {
		
		
	    XUserGroupInfo ugInfo = new XUserGroupInfo();
		
		ugInfo.setUserId(aUserInfo.getId());
		
		ugInfo.setGroupName(aGroupInfo.getName());
		
		// ugInfo.setParentGroupId("1");
		
        return ugInfo;
	}

	private UserGroupInfo getUsergroupInfo(UserGroupInfo ret) {
		Client c = getClient();
		
		WebResource r = c.resource(getURL(PM_ADD_USER_GROUP_INFO_URI));
		
		Gson gson = new GsonBuilder().create();
		
		String jsonString = gson.toJson(usergroupInfo);
		
		LOG.debug("USER GROUP MAPPING" + jsonString);
		
		String response = r.accept(MediaType.APPLICATION_JSON_TYPE).type(MediaType.APPLICATION_JSON_TYPE).post(String.class, jsonString);
		
		LOG.debug("RESPONSE: [" + response + "]");
		
		ret = gson.fromJson(response, UserGroupInfo.class);
		
		return ret;
	}

	@Override
	public void addOrUpdateGroup(String groupName, List<String> users) throws Throwable {
		// First get the existing group user mappings from Ranger admin.
		// Then compute the delta and send the updated group user mappings to ranger admin.
		LOG.debug("addOrUpdateGroup for " + groupName + " with users: " + users);
		GroupUserInfo groupUserInfo = null;
		if (authenticationType != null && AUTH_KERBEROS.equalsIgnoreCase(authenticationType) && SecureClientLogin.isKerberosCredentialExists(principal,keytab)) {
			try {
				LOG.info("Using principal = " + principal + " and keytab = " + keytab);
				Subject sub = SecureClientLogin.loginUserFromKeytab(principal, keytab, nameRules);
				final String gName = groupName;
				groupUserInfo = Subject.doAs(sub, new PrivilegedAction<GroupUserInfo>() {
					@Override
					public GroupUserInfo run() {
						try {
							return getGroupUserInfo(gName);
						} catch (Exception e) {
							LOG.error("Failed to build Group List : ", e);
						}
						return null;
					}
				});
			} catch (Exception e) {
				LOG.error("Failed to Authenticate Using given Principal and Keytab : ", e);
			}
		} else {
			groupUserInfo = getGroupUserInfo(groupName);
		}	
		
		List<String> oldUsers = new ArrayList<String>();
		if (groupUserInfo.getXuserInfo() != null) {
			for (XUserInfo xUserInfo : groupUserInfo.getXuserInfo()) {
				oldUsers.add(xUserInfo.getName());<commit_msg>RANGER-1345: Added a null check in order to avoid possible Null pointer dereference errors<commit_after>/*
 * 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.ranger.ldapusersync.process;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.PrivilegedAction;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.Subject;
import javax.ws.rs.core.MediaType;

import org.apache.hadoop.security.SecureClientLogin;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.ranger.unixusersync.config.UserGroupSyncConfig;
import org.apache.ranger.unixusersync.model.GroupUserInfo;
import org.apache.ranger.unixusersync.model.MUserInfo;
import org.apache.ranger.unixusersync.model.UserGroupInfo;
import org.apache.ranger.unixusersync.model.XGroupInfo;
import org.apache.ranger.unixusersync.model.XUserGroupInfo;
import org.apache.ranger.unixusersync.model.XUserInfo;
import org.apache.ranger.usergroupsync.UserGroupSink;
import org.apache.ranger.usersync.util.UserSyncUtil;

import com.google.common.collect.Table;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.jersey.client.urlconnection.HTTPSProperties;

public class LdapPolicyMgrUserGroupBuilder implements UserGroupSink {

private static final Logger LOG = Logger.getLogger(LdapPolicyMgrUserGroupBuilder.class);
	
	private static final String AUTHENTICATION_TYPE = "hadoop.security.authentication";	
	private String AUTH_KERBEROS = "kerberos";
	private static final String PRINCIPAL = "ranger.usersync.kerberos.principal";
	private static final String KEYTAB = "ranger.usersync.kerberos.keytab";
	private static final String NAME_RULE = "hadoop.security.auth_to_local";
	
	public static final String PM_USER_LIST_URI  = "/service/xusers/users/";				// GET
	private static final String PM_ADD_USER_GROUP_INFO_URI = "/service/xusers/users/userinfo";	// POST
	
	private static final String PM_ADD_GROUP_USER_INFO_URI = "/service/xusers/groups/groupinfo";	// POST
	
	public static final String PM_GROUP_LIST_URI = "/service/xusers/groups/";				// GET
	private static final String PM_ADD_GROUP_URI = "/service/xusers/groups/";				// POST
	
	private static final String PM_DEL_USER_GROUP_LINK_URI = "/service/xusers/group/${groupName}/user/${userName}"; // DELETE
	
	public static final String PM_USER_GROUP_MAP_LIST_URI = "/service/xusers/groupusers/";		// GET
	
	public static final String PM_GET_GROUP_USER_MAP_LIST_URI = "/service/xusers/groupusers/groupName/${groupName}";		// GET
	
	private static final String PM_ADD_LOGIN_USER_URI = "/service/users/default";			// POST
	private static final String GROUP_SOURCE_EXTERNAL ="1";
	private static String LOCAL_HOSTNAME = "unknown";
	private boolean isMockRun = false;
	private String policyMgrBaseUrl;
	
	private UserGroupSyncConfig  config = UserGroupSyncConfig.getInstance();

	private UserGroupInfo				usergroupInfo = new UserGroupInfo();
	private GroupUserInfo				groupuserInfo = new GroupUserInfo();
	
	Table<String, String, String> groupsUsersTable;
	
	private String keyStoreFile =  null;
	private String keyStoreFilepwd = null;
	private String trustStoreFile = null;
	private String trustStoreFilepwd = null;
	private String keyStoreType = null;
	private String trustStoreType = null;
	private HostnameVerifier hv =  null;

	private SSLContext sslContext = null;
	private String authenticationType = null;
	String principal;
	String keytab;
	String nameRules;
	
	static {
		try {
			LOCAL_HOSTNAME = java.net.InetAddress.getLocalHost().getCanonicalHostName();
		} catch (UnknownHostException e) {
			LOCAL_HOSTNAME = "unknown";
		}
	}
	
	synchronized public void init() throws Throwable {
		policyMgrBaseUrl = config.getPolicyManagerBaseURL();
		isMockRun = config.isMockRunEnabled();
		
		if (isMockRun) {
			LOG.setLevel(Level.DEBUG);
		}
		
		keyStoreFile =  config.getSSLKeyStorePath();
		keyStoreFilepwd = config.getSSLKeyStorePathPassword();
		trustStoreFile = config.getSSLTrustStorePath();
		trustStoreFilepwd = config.getSSLTrustStorePathPassword();
		keyStoreType = KeyStore.getDefaultType();
		trustStoreType = KeyStore.getDefaultType();
		authenticationType = config.getProperty(AUTHENTICATION_TYPE,"simple");
		try {
			principal = SecureClientLogin.getPrincipal(config.getProperty(PRINCIPAL,""), LOCAL_HOSTNAME);
		} catch (IOException ignored) {
			 // do nothing
		}
		keytab = config.getProperty(KEYTAB,"");
		nameRules = config.getProperty(NAME_RULE,"DEFAULT");

	}

	@Override
	public void addOrUpdateUser(String userName, List<String> groups) throws Throwable {
		//* Add user to groups mapping in the x_user table. 
		//* Here the assumption is that the user already exists in x_portal_user table.
		if ( ! isMockRun ) {
			// If the rest call to ranger admin fails, 
			// propagate the failure to the caller for retry in next sync cycle.
			if (addUserGroupInfo(userName, groups) == null ) {
				String msg = "Failed to add addorUpdate user group info";
				LOG.error(msg);
				throw new Exception(msg);
			}
		}

	}

	@Override
	public void addOrUpdateGroup(String groupName) throws Throwable {
		//* Build the group info object and do the rest call
			if ( ! isMockRun ) {
				if ( addGroupInfo(groupName) == null) {
					String msg = "Failed to add addorUpdate group info";
					LOG.error(msg);
					throw new Exception(msg);
				}
			}

	}
	
	private XGroupInfo addGroupInfo(final String groupName){
		XGroupInfo ret = null;
		XGroupInfo group = null;
		
		LOG.debug("INFO: addPMXAGroup(" + groupName + ")" );
		if (! isMockRun) {
			group = addXGroupInfo(groupName);
		}
		if (authenticationType != null && AUTH_KERBEROS.equalsIgnoreCase(authenticationType) && SecureClientLogin.isKerberosCredentialExists(principal,keytab)) {
			try {
				LOG.info("Using principal = " + principal + " and keytab = " + keytab);
				Subject sub = SecureClientLogin.loginUserFromKeytab(principal, keytab, nameRules);
				final XGroupInfo groupInfo = group;
				ret = Subject.doAs(sub, new PrivilegedAction<XGroupInfo>() {
					@Override
					public XGroupInfo run() {
						try {
							return getAddedGroupInfo(groupInfo);
						} catch (Exception e) {
							LOG.error("Failed to build Group List : ", e);
						}
						return null;
					}
				});
				return ret;
			} catch (Exception e) {
				LOG.error("Failed to Authenticate Using given Principal and Keytab : ", e);
			}
			return null;
		} else {
			return getAddedGroupInfo(group);
		}	
	}
	
	private XGroupInfo addXGroupInfo(String aGroupName) {
		
		XGroupInfo addGroup = new XGroupInfo();
		
		addGroup.setName(aGroupName);
		
		addGroup.setDescription(aGroupName + " - add from Unix box");
		
		addGroup.setGroupType("1");

		addGroup.setGroupSource(GROUP_SOURCE_EXTERNAL);
		groupuserInfo.setXgroupInfo(addGroup);

		return addGroup;
	}

	private XGroupInfo getAddedGroupInfo(XGroupInfo group){	
		XGroupInfo ret = null;
		
		Client c = getClient();
		
		WebResource r = c.resource(getURL(PM_ADD_GROUP_URI));
		
		Gson gson = new GsonBuilder().create();
		
		String jsonString = gson.toJson(group);
		
		LOG.debug("Group" + jsonString);
		
		String response = r.accept(MediaType.APPLICATION_JSON_TYPE).type(MediaType.APPLICATION_JSON_TYPE).post(String.class, jsonString);
		
		LOG.debug("RESPONSE: [" + response + "]");
		
		ret = gson.fromJson(response, XGroupInfo.class);
		
		return ret;	
	}

	public static void main(String[] args) throws Throwable {
		LdapPolicyMgrUserGroupBuilder  ugbuilder = new LdapPolicyMgrUserGroupBuilder();
		ugbuilder.init();

	}

	@Override
	public void addOrUpdateUser(String userName) throws Throwable {
		// First add to x_portal_user
		LOG.debug("INFO: addPMAccount(" + userName + ")" );
		if (! isMockRun) {
			if (addMUser(userName) == null) {
		        String msg = "Failed to add portal user";
		        LOG.error(msg);
		        throw new Exception(msg);
			}
		}
		List<String> groups = new ArrayList<String>();
		//* Build the user group info object with empty groups and do the rest call
		if ( ! isMockRun ) {
			// If the rest call to ranger admin fails, 
			// propagate the failure to the caller for retry in next sync cycle.
			if (addUserGroupInfo(userName, groups) == null ) {
				String msg = "Failed to add addorUpdate user group info";
				LOG.error(msg);
				throw new Exception(msg);
			}
		}
		
	}
	
	private UserGroupInfo addUserGroupInfo(String userName, List<String> groups){
		if(LOG.isDebugEnabled()) {
	 		LOG.debug("==> LdapPolicyMgrUserGroupBuilder.addUserGroupInfo " + userName + " and groups");
	 	}
		UserGroupInfo ret = null;
		XUserInfo user = null;
		LOG.debug("INFO: addPMXAUser(" + userName + ")" );
		
		if (! isMockRun) {
			user = addXUserInfo(userName);
		}
		for(String g : groups) {
				LOG.debug("INFO: addPMXAGroupToUser(" + userName + "," + g + ")" );
		}
		if (! isMockRun ) {
			addXUserGroupInfo(user, groups);
		}
		if (authenticationType != null && AUTH_KERBEROS.equalsIgnoreCase(authenticationType) && SecureClientLogin.isKerberosCredentialExists(principal, keytab)){
			try {
				Subject sub = SecureClientLogin.loginUserFromKeytab(principal, keytab, nameRules);
				final UserGroupInfo result = ret;
				ret = Subject.doAs(sub, new PrivilegedAction<UserGroupInfo>() {
					@Override
					public UserGroupInfo run() {
						try {
							return getUsergroupInfo(result);
						} catch (Exception e) {
							LOG.error("Failed to add User Group Info : ", e);
						}
						return null;
					}
				});
				return ret;
			} catch (Exception e) {
				LOG.error("Failed to Authenticate Using given Principal and Keytab : " , e);
			}
			return null;
		}else{
			return getUsergroupInfo(ret);
		}
	}
	
	private XUserInfo addXUserInfo(String aUserName) {
		
		XUserInfo xuserInfo = new XUserInfo();

		xuserInfo.setName(aUserName);
		
		xuserInfo.setDescription(aUserName + " - add from Unix box");
	   	
		usergroupInfo.setXuserInfo(xuserInfo);
		
		return xuserInfo;
	}

	private void addXUserGroupInfo(XUserInfo aUserInfo, List<String> aGroupList) {
		
		List<XGroupInfo> xGroupInfoList = new ArrayList<XGroupInfo>();
		
		for(String groupName : aGroupList) {
			XGroupInfo group = addXGroupInfo(groupName);
			xGroupInfoList.add(group);
			addXUserGroupInfo(aUserInfo, group);
		}
		
		usergroupInfo.setXgroupInfo(xGroupInfoList);
	}
	
	private XUserGroupInfo addXUserGroupInfo(XUserInfo aUserInfo, XGroupInfo aGroupInfo) {
		
		
	    XUserGroupInfo ugInfo = new XUserGroupInfo();
		
		ugInfo.setUserId(aUserInfo.getId());
		
		ugInfo.setGroupName(aGroupInfo.getName());
		
		// ugInfo.setParentGroupId("1");
		
        return ugInfo;
	}

	private UserGroupInfo getUsergroupInfo(UserGroupInfo ret) {
		Client c = getClient();
		
		WebResource r = c.resource(getURL(PM_ADD_USER_GROUP_INFO_URI));
		
		Gson gson = new GsonBuilder().create();
		
		String jsonString = gson.toJson(usergroupInfo);
		
		LOG.debug("USER GROUP MAPPING" + jsonString);
		
		String response = r.accept(MediaType.APPLICATION_JSON_TYPE).type(MediaType.APPLICATION_JSON_TYPE).post(String.class, jsonString);
		
		LOG.debug("RESPONSE: [" + response + "]");
		
		ret = gson.fromJson(response, UserGroupInfo.class);
		
		return ret;
	}

	@Override
	public void addOrUpdateGroup(String groupName, List<String> users) throws Throwable {
		// First get the existing group user mappings from Ranger admin.
		// Then compute the delta and send the updated group user mappings to ranger admin.
		LOG.debug("addOrUpdateGroup for " + groupName + " with users: " + users);
		GroupUserInfo groupUserInfo = null;
		if (authenticationType != null && AUTH_KERBEROS.equalsIgnoreCase(authenticationType) && SecureClientLogin.isKerberosCredentialExists(principal,keytab)) {
			try {
				LOG.info("Using principal = " + principal + " and keytab = " + keytab);
				Subject sub = SecureClientLogin.loginUserFromKeytab(principal, keytab, nameRules);
				final String gName = groupName;
				groupUserInfo = Subject.doAs(sub, new PrivilegedAction<GroupUserInfo>() {
					@Override
					public GroupUserInfo run() {
						try {
							return getGroupUserInfo(gName);
						} catch (Exception e) {
							LOG.error("Failed to build Group List : ", e);
						}
						return null;
					}
				});
			} catch (Exception e) {
				LOG.error("Failed to Authenticate Using given Principal and Keytab : ", e);
			}
		} else {
			groupUserInfo = getGroupUserInfo(groupName);
		}	
		
		List<String> oldUsers = new ArrayList<String>();
		if (groupUserInfo != null && groupUserInfo.getXuserInfo() != null) {
			for (XUserInfo xUserInfo : groupUserInfo.getXuserInfo()) {
				oldUsers.add(xUserInfo.getName());
{'fraction_non_alphanumeric': 0.10316504991857255, 'fraction_numerical': 0.0007788713446151668, 'mean_word_length': 4.146110402623429, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4049768', 'n_tokens_mistral': 9883, 'n_tokens_neox': 8668, 'n_words': 2122}