diff --git a/distro/changelog.xml b/distro/changelog.xml index b3f8cd0..d4c45ba 100644 --- a/distro/changelog.xml +++ b/distro/changelog.xml @@ -1,4 +1,7 @@ + + Feature #6094 User export to LDAP on create account and join/leave VRE + Feature #8242 Enhance VRE Folder hook to also create Share LaTex user on user add to VRE diff --git a/pom.xml b/pom.xml index eefefde..7c51aaa 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ org.gcube.portal.plugins VREFolder-hook VREFolder-hook Hook - 6.5.0-SNAPSHOT + 6.6.0-SNAPSHOT war VREFolder-hook handles the user adding/removal from the related Home Library VRE Folder diff --git a/src/main/java/org/gcube/portal/plugins/GCubeHookUserLocalService.java b/src/main/java/org/gcube/portal/plugins/GCubeHookUserLocalService.java index fa6a7a2..5269d52 100644 --- a/src/main/java/org/gcube/portal/plugins/GCubeHookUserLocalService.java +++ b/src/main/java/org/gcube/portal/plugins/GCubeHookUserLocalService.java @@ -4,13 +4,14 @@ import org.gcube.common.homelibrary.home.HomeLibrary; import org.gcube.common.portal.PortalContext; import org.gcube.common.scope.api.ScopeProvider; import org.gcube.portal.plugins.thread.CheckShareLatexUserThread; +import org.gcube.portal.plugins.thread.UpdateUserToLDAPGroupThread; import org.gcube.vomanagement.usermanagement.GroupManager; import org.gcube.vomanagement.usermanagement.impl.LiferayGroupManager; import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.liferay.portal.kernel.exception.SystemException; +import com.liferay.portal.kernel.log.Log; +import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.model.User; import com.liferay.portal.service.UserLocalService; import com.liferay.portal.service.UserLocalServiceWrapper; @@ -23,7 +24,7 @@ public class GCubeHookUserLocalService extends UserLocalServiceWrapper { /** * logger */ - private static final Logger _log = LoggerFactory.getLogger(GCubeHookUserLocalService.class); + private static Log _log = LogFactoryUtil.getLog(GCubeHookUserLocalService.class); /* (non-Java-doc) * @see com.liferay.portal.service.UserLocalServiceWrapper#UserLocalServiceWrapper(UserLocalService userLocalService) */ @@ -118,11 +119,14 @@ public class GCubeHookUserLocalService extends UserLocalServiceWrapper { String scope = gm.getInfrastructureScope(groupId); org.gcube.vomanagement.usermanagement.UserManager um = new LiferayUserManager(); String username = um.getUserById(userId).getUsername(); - org.gcube.common.homelibrary.home.workspace.usermanager.UserManager hlUm = HomeLibrary.getHomeManagerFactory().getUserManager(); - hlUm.associateUserToGroup(scope, username); + //add the user to LDAP Group + Thread tLdap = new Thread(new UpdateUserToLDAPGroupThread(username, scope, groupId, false)); + tLdap.start(); //add the user to shareLatex Thread t = new Thread(new CheckShareLatexUserThread(username, scope)); t.start(); + org.gcube.common.homelibrary.home.workspace.usermanager.UserManager hlUm = HomeLibrary.getHomeManagerFactory().getUserManager(); + hlUm.associateUserToGroup(scope, username); } else { _log.debug("Group is not a VRE, SKIP adding"); } @@ -159,6 +163,9 @@ public class GCubeHookUserLocalService extends UserLocalServiceWrapper { String scope = gm.getInfrastructureScope(groupId); org.gcube.vomanagement.usermanagement.UserManager um = new LiferayUserManager(); String username = um.getUserById(userId).getUsername(); + //remove the user to LDAP Group + Thread tLdap = new Thread(new UpdateUserToLDAPGroupThread(username, scope, groupId, true)); + tLdap.start(); org.gcube.common.homelibrary.home.workspace.usermanager.UserManager hlUm = HomeLibrary.getHomeManagerFactory().getUserManager(); hlUm.removeUserFromGroup(scope, username); } else { diff --git a/src/main/java/org/gcube/portal/plugins/bean/LDAPInfo.java b/src/main/java/org/gcube/portal/plugins/bean/LDAPInfo.java new file mode 100644 index 0000000..e39b4a1 --- /dev/null +++ b/src/main/java/org/gcube/portal/plugins/bean/LDAPInfo.java @@ -0,0 +1,64 @@ +package org.gcube.portal.plugins.bean; + +public class LDAPInfo { + private String ldapUrl,ldapPassword,filter,principal; + + public LDAPInfo() { + super(); + } + public LDAPInfo(String ldapUrl, String ldapPassword, String filter, String principal) { + super(); + this.ldapUrl = ldapUrl; + this.ldapPassword = ldapPassword; + this.filter = filter; + this.principal = principal; + } + + public String getLdapUrl() { + return ldapUrl; + } + + public void setLdapUrl(String ldapUrl) { + this.ldapUrl = ldapUrl; + } + + public String getLdapPassword() { + return ldapPassword; + } + + public void setLdapPassword(String ldapPassword) { + this.ldapPassword = ldapPassword; + } + + public String getFilter() { + return filter; + } + + public void setFilter(String filter) { + this.filter = filter; + } + + public String getPrincipal() { + return principal; + } + + public void setPrincipal(String principal) { + this.principal = principal; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("LDAPInfo [ldapUrl="); + builder.append(ldapUrl); + builder.append(", ldapPassword="); + builder.append(ldapPassword); + builder.append(", filter="); + builder.append(filter); + builder.append(", principal="); + builder.append(principal); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/src/main/java/org/gcube/portal/plugins/thread/CheckShareLatexUserThread.java b/src/main/java/org/gcube/portal/plugins/thread/CheckShareLatexUserThread.java index 1a73526..bed2698 100644 --- a/src/main/java/org/gcube/portal/plugins/thread/CheckShareLatexUserThread.java +++ b/src/main/java/org/gcube/portal/plugins/thread/CheckShareLatexUserThread.java @@ -13,8 +13,9 @@ import org.gcube.common.authorization.library.provider.UserInfo; import org.gcube.common.resources.gcore.GCoreEndpoint; import org.gcube.common.resources.gcore.GCoreEndpoint.Profile.Endpoint; import org.gcube.common.scope.api.ScopeProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import com.liferay.portal.kernel.log.Log; +import com.liferay.portal.kernel.log.LogFactoryUtil; /** * @@ -22,7 +23,8 @@ import org.slf4j.LoggerFactory; * */ public class CheckShareLatexUserThread implements Runnable { - private static final Logger _log = LoggerFactory.getLogger(CheckShareLatexUserThread.class); + + private static Log _log = LogFactoryUtil.getLog(CheckShareLatexUserThread.class); private static final String SERVICE_NAME = "ShareLatex"; private static final String SERVICE_CLASS = "DataAccess"; diff --git a/src/main/java/org/gcube/portal/plugins/thread/UpdateUserToLDAPGroupThread.java b/src/main/java/org/gcube/portal/plugins/thread/UpdateUserToLDAPGroupThread.java new file mode 100644 index 0000000..263d275 --- /dev/null +++ b/src/main/java/org/gcube/portal/plugins/thread/UpdateUserToLDAPGroupThread.java @@ -0,0 +1,143 @@ +package org.gcube.portal.plugins.thread; +import java.util.Properties; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.plugins.bean.LDAPInfo; +import org.gcube.portal.plugins.util.LDAPUtil; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.impl.LiferayGroupManager; +import org.gcube.vomanagement.usermanagement.model.GCubeGroup; + +import com.liferay.portal.kernel.log.Log; +import com.liferay.portal.kernel.log.LogFactoryUtil; + +/** + * + * @author Massimiliano Assante ISTI-CNR + * + */ +public class UpdateUserToLDAPGroupThread implements Runnable { + private static Log _log = LogFactoryUtil.getLog(UpdateUserToLDAPGroupThread.class); + + + private String username; + private String scope; + private long vreGroupId; + private boolean remove; + /** + * + * @param username + * @param scope + * @param vreGroupId + * @param remove set true to remove the user from the group, false to add the user + */ + public UpdateUserToLDAPGroupThread(String username, String scope, long vreGroupId, boolean remove) { + super(); + this.username = username; + this.scope = scope; + this.vreGroupId = vreGroupId; + this.remove = remove; + } + + @Override + public void run() { + String currScope = ScopeProvider.instance.get(); + ScopeProvider.instance.set(scope); + + LDAPInfo info = LDAPUtil.getLDAPCoordinates(); + GroupManager gm = new LiferayGroupManager(); + + + Properties env = new Properties(); + env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, info.getLdapUrl()); + env.put(Context.SECURITY_PRINCIPAL, info.getPrincipal()); + env.put(Context.SECURITY_CREDENTIALS, info.getLdapPassword()); + + try { + GCubeGroup root = LDAPUtil.getRootVO(); + GCubeGroup vre = gm.getGroup(vreGroupId); + GCubeGroup vo = gm.getGroup(vre.getParentGroupId()); + + + DirContext ctx = new InitialDirContext(env); + + String subCtx = LDAPUtil.getOrgSubContext(root.getGroupName()); + String orgSubCtx = "ou="+vo.getGroupName()+","+subCtx; + String vreSubCtx = "cn="+vre.getGroupName()+","+orgSubCtx; + //if the VRE is very NEW the LDAP Group may not exist + if (!LDAPUtil.checkIfLDAPGroupExists(ctx, vreSubCtx)) + LDAPUtil.createGroupVRE(ctx, vreSubCtx, vre.getGroupName()); + //here update the list of users in such VRE with the username + if (remove) + removeUserFromGroup(username, ctx, vreSubCtx, vre); + else + addUsertoGroup(username, ctx, vreSubCtx, vre); + } catch (NamingException e) { + _log.error("Something went Wrong during UpdateUserToLDAPGroupThread"); + e.printStackTrace(); + } catch (Exception es) { + _log.error("Something went Wrong during UpdateUserToLDAPGroupThread in retrieving Liferay Organization"); + es.printStackTrace(); + } finally { + ScopeProvider.instance.set(currScope); + } + ScopeProvider.instance.set(currScope); //restore the scope in ThreadLocal + } + + /** + * + * @param username + * @param ctx + * @param vreSubCtx + * @param vre + */ + private static void addUsertoGroup(String username, DirContext ctx, String vreSubCtx, GCubeGroup vre) { + String user = username; + try { + Attribute memberUid = new BasicAttribute("memberUid"); + memberUid.add(user); + Attributes attributes = new BasicAttributes(); + attributes.put(memberUid); + ctx.modifyAttributes(vreSubCtx, DirContext.ADD_ATTRIBUTE, attributes); + _log.info("Added user: " + user + " to VRE: " + vre.getGroupName()); + } + catch (NamingException ex) { + _log.warn("Not adding already existing user: " + user); + } + } + + /** + * + * @param username + * @param ctx + * @param vreSubCtx + * @param vre + */ + private static void removeUserFromGroup(String username, DirContext ctx, String vreSubCtx, GCubeGroup vre) { + String user = username; + try { + Attribute memberUid = new BasicAttribute("memberUid"); + memberUid.add(user); + Attributes attributes = new BasicAttributes(); + attributes.put(memberUid); + ctx.modifyAttributes(vreSubCtx, DirContext.REMOVE_ATTRIBUTE, attributes); + _log.info("Removed user: " + user + " from VRE: " + vre.getGroupName()); + } + catch (NamingException ex) { + _log.warn("Not removing, not existing user? " + user); + } + } + +} + + diff --git a/src/main/java/org/gcube/portal/plugins/util/LDAPUtil.java b/src/main/java/org/gcube/portal/plugins/util/LDAPUtil.java new file mode 100644 index 0000000..842a31e --- /dev/null +++ b/src/main/java/org/gcube/portal/plugins/util/LDAPUtil.java @@ -0,0 +1,207 @@ +package org.gcube.portal.plugins.util; + +import static org.gcube.resources.discovery.icclient.ICFactory.clientFor; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +import java.util.List; +import java.util.Random; + +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +import org.gcube.common.encryption.StringEncrypter; +import org.gcube.common.portal.PortalContext; +import org.gcube.common.resources.gcore.ServiceEndpoint; +import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint; +import org.gcube.common.resources.gcore.ServiceEndpoint.Property; +import org.gcube.common.resources.gcore.utils.Group; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.plugins.bean.LDAPInfo; +import org.gcube.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.SimpleQuery; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.impl.LiferayGroupManager; +import org.gcube.vomanagement.usermanagement.model.GCubeGroup; + +import com.liferay.portal.kernel.log.Log; +import com.liferay.portal.kernel.log.LogFactoryUtil; + +public class LDAPUtil { + private static Log _log = LogFactoryUtil.getLog(LDAPUtil.class); + public static final String LDAP_SERVER_NAME = "LDAPServer"; + public static final String LDAP_SERVER_FILTER_NAME = "filter"; + public static final String LDAP_SERVER_PRINCPAL_NAME = "ldapPrincipal"; + + public static final String LDAP_ORG_FILTER = "(objectClass=organizationalUnit)"; + public static final String LDAP_GROUP_FILTER = "(objectClass=posixGroup)"; + public static final String USER_CONTEXT = ",ou=People,o=D4Science,ou=Organizations,dc=d4science,dc=org"; + public static final String DEFAULT_GID_NUMBER = "1000"; + /** + * + * @param ctx + * @param groupSubctx + * @return true if exists + */ + public static boolean checkIfLDAPGroupExists(DirContext ctx, String groupSubctx) { + SearchControls ctls = new SearchControls(); + ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); + NamingEnumeration answer; + try { + answer = ctx.search(groupSubctx, LDAP_GROUP_FILTER, ctls); + } catch (NamingException e) { + _log.debug("not found in LDAP (will add it): Group: " + groupSubctx); + return false; + } + boolean toReturn = answer.hasMoreElements(); + _log.debug("Group: " + groupSubctx + " exists? " + toReturn); + return toReturn; + } + /** + * + * @param ctx + * @param subContext + * @param vreName + * @throws NamingException + */ + public static void createGroupVRE(DirContext ctx, String subContext, String vreName) throws NamingException { + Attributes attributes = new BasicAttributes(); + + Attribute objectClass = new BasicAttribute("objectClass"); + objectClass.add("top"); + objectClass.add("posixGroup"); + attributes.put(objectClass); + + Attribute cn = new BasicAttribute("cn"); + cn.add(vreName); + attributes.put(cn); + + Attribute gidNumber = new BasicAttribute("gidNumber"); + gidNumber.add(String.valueOf(getRandomPOSIXidentifier())); + attributes.put(gidNumber); + + ctx.createSubcontext(subContext, attributes); + _log.info("Added " + subContext); + } + /** + * ask to the Information System + * @return + */ + public static LDAPInfo getLDAPCoordinates() { + LDAPInfo toReturn = new LDAPInfo(); + @SuppressWarnings("deprecation") + String portalName = PortalContext.getPortalInstanceName(); + + PortalContext context = PortalContext.getConfiguration(); + String scope = "/" + context.getInfrastructureName(); + ScopeProvider.instance.set(scope); + + SimpleQuery query = queryFor(ServiceEndpoint.class); + query.addCondition("$resource/Profile/Category/text() eq 'Portal'"); + query.addCondition("$resource/Profile/Name/text() eq '" + portalName + "'"); + + DiscoveryClient client = clientFor(ServiceEndpoint.class); + + List list = client.submit(query); + if (list == null || list.isEmpty()) { + _log.error("Could not find any Service endpoint registred in the infrastructure for this portal: " + portalName); + } + else if (list.size() > 1) { + _log.warn("Found more than one Service endpoint registred in the infrastructure for this portal: " + portalName); + } + else { + for (ServiceEndpoint res : list) { + Group apGroup = res.profile().accessPoints(); + AccessPoint[] accessPoints = (AccessPoint[]) apGroup.toArray(new AccessPoint[apGroup.size()]); + for (int i = 0; i < accessPoints.length; i++) { + if (accessPoints[i].name().compareTo(LDAP_SERVER_NAME) == 0) { + _log.info("Found credentials for " + LDAP_SERVER_NAME); + AccessPoint found = accessPoints[i]; + String ldapUrl = found.address(); + toReturn.setLdapUrl(ldapUrl); + String encrPassword = found.password(); + try { + String ldapPassword = StringEncrypter.getEncrypter().decrypt( encrPassword); + toReturn.setLdapPassword(ldapPassword); + } catch (Exception e) { + _log.error("Something went wrong while decrypting password for " + LDAP_SERVER_NAME); + e.printStackTrace(); + } + Group propGroup = found.properties(); + Property[] props = (Property[]) propGroup.toArray(new Property[propGroup.size()]); + for (int j = 0; j < props.length; j++) { + if (props[j].name().compareTo(LDAP_SERVER_FILTER_NAME) == 0) { + _log.info("\tFound properties of " + LDAP_SERVER_FILTER_NAME); + String encrValue = props[j].value(); + System.out.println("Filter encrypted = " + encrValue); + try { + String filter = StringEncrypter.getEncrypter().decrypt(encrValue); + toReturn.setFilter(filter); + } catch (Exception e) { + _log.error("Something went wrong while decrypting value for " + LDAP_SERVER_FILTER_NAME); + e.printStackTrace(); + } + } + else if (props[j].name().compareTo(LDAP_SERVER_PRINCPAL_NAME) == 0) { + _log.info("\tFound properties of " + LDAP_SERVER_PRINCPAL_NAME); + String encrValue = props[j].value(); + try { + String principal = StringEncrypter.getEncrypter().decrypt(encrValue); + toReturn.setPrincipal(principal); + } catch (Exception e) { + _log.error("Something went wrong while decrypting value for " + LDAP_SERVER_PRINCPAL_NAME); + e.printStackTrace(); + } + } + } + + } + } + + } + } + return toReturn; + } + /** + * + * @return the Liferay mapped as Root Organization + */ + public static GCubeGroup getRootVO() { + try { + GroupManager gm = new LiferayGroupManager(); + String rootVoName = gm.getRootVOName(); + _log.debug("Root organization name found: " + rootVoName); + return gm.getGroup(gm.getGroupIdFromInfrastructureScope("/"+rootVoName)); + } + catch (Exception e) { + _log.error("There were problems retrieving root VO group", e); + } + _log.error("Could not find any root organization"); + return null; + } + /*** + * + * @param orgName + * @return + */ + public static String getOrgSubContext(String orgName) { + return "ou="+orgName+",dc=d4science,dc=org"; + } + /** + * + * @return an integer between 1000 and 2147483647 + */ + public static int getRandomPOSIXidentifier() { + final int Low = 1000; + final int High = 2147483647; + Random r = new Random(); + int toReturn = r.nextInt(High-Low) + Low; + return toReturn; + } +} diff --git a/src/main/webapp/WEB-INF/liferay-plugin-package.properties b/src/main/webapp/WEB-INF/liferay-plugin-package.properties index b154ce5..1f6d959 100644 --- a/src/main/webapp/WEB-INF/liferay-plugin-package.properties +++ b/src/main/webapp/WEB-INF/liferay-plugin-package.properties @@ -1,6 +1,6 @@ name=VREFolder-hook module-group-id=liferay -module-incremental-version=3 +module-incremental-version=4 tags= short-description= change-log=