diff --git a/jeeves/src/main/java/jeeves/resources/dbms/Dbms.java b/jeeves/src/main/java/jeeves/resources/dbms/Dbms.java index 69c8753702..a43c17e265 100644 --- a/jeeves/src/main/java/jeeves/resources/dbms/Dbms.java +++ b/jeeves/src/main/java/jeeves/resources/dbms/Dbms.java @@ -25,9 +25,11 @@ import jeeves.constants.Jeeves; import jeeves.utils.Log; + import org.jdom.Element; import javax.sql.DataSource; + import java.io.StringReader; import java.sql.Connection; import java.sql.Date; @@ -35,6 +37,7 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.sql.Savepoint; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; @@ -73,7 +76,7 @@ public Dbms(DataSource dataSource, String url) throws ClassNotFoundException this.dataSource = dataSource; this.url = url; } - + //-------------------------------------------------------------------------- //--- //--- Connection methods @@ -215,7 +218,7 @@ public Element selectFull(String query, Map formats, Object... a } long start = System.currentTimeMillis(); resultSet = stmt.executeQuery(); - + Element result = buildResponse(resultSet, formats); long end = System.currentTimeMillis(); @@ -290,7 +293,7 @@ public int execute(String query, Object... args) throws SQLException } finally { - if(stmt != null) { + if(stmt != null) { stmt.close(); } } @@ -446,7 +449,7 @@ private String stripIllegalChars(String input) { output = output.replaceAll(c, ""); } } - + return output; } @@ -482,16 +485,24 @@ private String getArgs(Object[] args) return sb.toString(); } - + /** * In case DBMS connection was not closed - * due to some error, close connection on + * due to some error, close connection on * finalize. */ protected void finalize() { disconnect(); } - + + public Savepoint setSavePoint() throws SQLException { + return conn.setSavepoint(); + } + + public void rollbackToSavepoint(Savepoint sp) throws SQLException { + conn.rollback(sp); + } + } //============================================================================= diff --git a/web/src/main/java/org/fao/geonet/kernel/security/ldap/LDAPSynchronizerJob.java b/web/src/main/java/org/fao/geonet/kernel/security/ldap/LDAPSynchronizerJob.java index c34a7aa35b..2e2567bfb9 100644 --- a/web/src/main/java/org/fao/geonet/kernel/security/ldap/LDAPSynchronizerJob.java +++ b/web/src/main/java/org/fao/geonet/kernel/security/ldap/LDAPSynchronizerJob.java @@ -26,6 +26,7 @@ import jeeves.server.resources.ResourceManager; import jeeves.utils.Log; import jeeves.utils.SerialFactory; + import org.fao.geonet.constants.Geonet; import org.jdom.Element; import org.quartz.JobDataMap; @@ -39,16 +40,18 @@ import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.SearchResult; + import java.sql.SQLException; +import java.sql.Savepoint; import java.util.regex.Matcher; import java.util.regex.Pattern; public class LDAPSynchronizerJob extends QuartzJobBean { - + private ApplicationContext applicationContext; - + private DefaultSpringSecurityContextSource contextSource; - + @Override protected void executeInternal(JobExecutionContext jobExecContext) throws JobExecutionException { @@ -56,54 +59,54 @@ protected void executeInternal(JobExecutionContext jobExecContext) if (Log.isDebugEnabled(Geonet.LDAP)) { Log.debug(Geonet.LDAP, "LDAPSynchronizerJob starting ..."); } - + // Retrieve application context. A defautl SpringBeanJobFactory // will not provide the application context to the job. Use // AutowiringSpringBeanJobFactory. applicationContext = (ApplicationContext) jobExecContext .getJobDetail().getJobDataMap().get("applicationContext"); - - + + if (applicationContext == null) { Log.error( Geonet.LDAP, " Application context is null. Be sure to configure SchedulerFactoryBean job factory property with AutowiringSpringBeanJobFactory."); } - + // Get LDAP information defining which users to sync final JobDataMap jdm = jobExecContext.getJobDetail() .getJobDataMap(); contextSource = (DefaultSpringSecurityContextSource) jdm .get("contextSource"); - + String ldapUserSearchFilter = (String) jdm .get("ldapUserSearchFilter"); String ldapUserSearchBase = (String) jdm.get("ldapUserSearchBase"); String ldapUserSearchAttribute = (String) jdm .get("ldapUserSearchAttribute"); - + DirContext dc = contextSource.getReadOnlyContext(); - + // Get database ResourceManager resourceManager = applicationContext .getBean(ResourceManager.class); Dbms dbms = null; - + try { dbms = (Dbms) resourceManager.openDirect(Geonet.Res.MAIN_DB); - + // Users synchronizeUser(ldapUserSearchFilter, ldapUserSearchBase, ldapUserSearchAttribute, dc, dbms); - + // And optionaly groups String createNonExistingLdapGroup = (String) jdm .get("createNonExistingLdapGroup"); - + if ("true".equals(createNonExistingLdapGroup)) { SerialFactory serialFactory = applicationContext .getBean(SerialFactory.class); - + String ldapGroupSearchFilter = (String) jdm .get("ldapGroupSearchFilter"); String ldapGroupSearchBase = (String) jdm @@ -112,7 +115,7 @@ protected void executeInternal(JobExecutionContext jobExecContext) .get("ldapGroupSearchAttribute"); String ldapGroupSearchPattern = (String) jdm .get("ldapGroupSearchPattern"); - + synchronizeGroup(ldapGroupSearchFilter, ldapGroupSearchBase, ldapGroupSearchAttribute, ldapGroupSearchPattern, dc, dbms, serialFactory); @@ -154,13 +157,13 @@ protected void executeInternal(JobExecutionContext jobExecContext) e); e.printStackTrace(); } - + if (Log.isDebugEnabled(Geonet.LDAP)) { Log.debug(Geonet.LDAP, "LDAPSynchronizerJob done."); } } - - + + private void synchronizeUser(String ldapUserSearchFilter, String ldapUserSearchBase, String ldapUserSearchAttribute, DirContext dc, Dbms dbms) throws NamingException, SQLException { @@ -168,7 +171,7 @@ private void synchronizeUser(String ldapUserSearchFilter, // in only. NamingEnumeration userList = dc.search(ldapUserSearchBase, ldapUserSearchFilter, null); - + // Build a list of LDAP users StringBuffer usernames = new StringBuffer(); while (userList.hasMore()) { @@ -178,7 +181,7 @@ private void synchronizeUser(String ldapUserSearchFilter, .get()); usernames.append("', "); } - + // Remove LDAP user available in db and not in LDAP if not linked to // metadata String query = "SELECT id FROM Users WHERE authtype=? AND username NOT IN (" @@ -188,25 +191,31 @@ private void synchronizeUser(String ldapUserSearchFilter, Element r = (Element) record; int userId = Integer.valueOf(r.getChildText("id")); Log.debug(Geonet.LDAP, " - Removing user: " + userId); + Savepoint sp = null; try { + sp = dbms.setSavePoint(); dbms.execute("DELETE FROM UserGroups WHERE userId=?", userId); dbms.execute("DELETE FROM Users WHERE authtype=? AND id=?", LDAPConstants.LDAP_FLAG, userId); + } catch (Exception ex) { Log.error(Geonet.LDAP, "Failed to remove LDAP user with id " + userId + " in database. User is probably a metadata owner." + " Transfer owner first.", ex); + if (sp != null) { + dbms.rollbackToSavepoint(sp); + } } } } - - + + private void synchronizeGroup(String ldapGroupSearchFilter, String ldapGroupSearchBase, String ldapGroupSearchAttribute, String ldapGroupSearchPattern, DirContext dc, Dbms dbms, SerialFactory serialFactory) throws NamingException, SQLException { - + NamingEnumeration groupList = dc.search(ldapGroupSearchBase, ldapGroupSearchFilter, null); Pattern ldapGroupSearchPatternCompiled = null; @@ -214,16 +223,16 @@ private void synchronizeGroup(String ldapGroupSearchFilter, ldapGroupSearchPatternCompiled = Pattern .compile(ldapGroupSearchPattern); } - + while (groupList.hasMore()) { SearchResult sr = (SearchResult) groupList.next(); - + // TODO : should we retrieve LDAP group id and do an update of group // name // This will require to store in local db the remote id String groupName = (String) sr.getAttributes() .get(ldapGroupSearchAttribute).get(); - + if (!"".equals(ldapGroupSearchPattern)) { Matcher m = ldapGroupSearchPatternCompiled.matcher(groupName); boolean b = m.matches(); @@ -231,14 +240,14 @@ private void synchronizeGroup(String ldapGroupSearchFilter, groupName = m.group(1); } } - + Element groupIdRequest = dbms.select( "SELECT id FROM Groups WHERE name = ?", groupName); Element groupRecord = groupIdRequest.getChild("record"); String groupId = null; - + if (groupRecord == null) { - LDAPUtils.createIfNotExist(groupName, groupId, dbms, serialFactory); + groupId = LDAPUtils.createIfNotExist(groupName, groupId, dbms, serialFactory); } else { groupId = groupRecord.getChildText("id"); @@ -248,11 +257,11 @@ private void synchronizeGroup(String ldapGroupSearchFilter, } } } - + public DefaultSpringSecurityContextSource getContextSource() { return contextSource; } - + public void setContextSource( DefaultSpringSecurityContextSource contextSource) { this.contextSource = contextSource; diff --git a/web/src/main/java/org/fao/geonet/kernel/security/ldap/LDAPUtils.java b/web/src/main/java/org/fao/geonet/kernel/security/ldap/LDAPUtils.java index f5a566b436..70122e7d68 100644 --- a/web/src/main/java/org/fao/geonet/kernel/security/ldap/LDAPUtils.java +++ b/web/src/main/java/org/fao/geonet/kernel/security/ldap/LDAPUtils.java @@ -25,6 +25,7 @@ import jeeves.resources.dbms.Dbms; import jeeves.utils.Log; import jeeves.utils.SerialFactory; + import org.fao.geonet.constants.Geonet; import org.fao.geonet.constants.Geonet.Profile; import org.fao.geonet.lib.Lib; @@ -34,7 +35,9 @@ import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; + import java.sql.SQLException; +import java.sql.Savepoint; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -42,14 +45,14 @@ public class LDAPUtils { /** * Save or update an LDAP user to the local GeoNetwork database. - * + * * TODO : test when a duplicate username is in the local DB and from an LDAP * Unique key constraint should return errors. - * + * * @param user * @param dbms - * @param serialFactory - * @throws Exception + * @param serialFactory + * @throws Exception */ static void saveUser(LDAPUser user, Dbms dbms, SerialFactory serialFactory, boolean importPrivilegesFromLdap, boolean createNonExistingLdapGroup) throws Exception { Element selectRequest = dbms.select("SELECT * FROM Users WHERE username=?", user.getUsername()); @@ -63,19 +66,19 @@ static void saveUser(LDAPUser user, Dbms dbms, SerialFactory serialFactory, bool if (Log.isDebugEnabled(Geonet.LDAP)){ Log.debug(Geonet.LDAP, " - Create LDAP user " + user.getUsername() + " in local database."); } - + id = serialFactory.getSerial(dbms, "Users") + ""; - + String query = "INSERT INTO Users (id, username, password, surname, name, profile, "+ "address, city, state, zip, country, email, organisation, kind, phone, authtype ) "+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - dbms.execute(query, new Integer(id), user.getUsername(), "", user.getSurname(), user.getName(), - user.getProfile(), user.getAddress(), user.getCity(), user.getState(), user.getZip(), - user.getCountry(), user.getEmail(), user.getOrganisation(), user.getKind(), + dbms.execute(query, new Integer(id), user.getUsername(), "", user.getSurname(), user.getName(), + user.getProfile(), user.getAddress(), user.getCity(), user.getState(), user.getZip(), + user.getCountry(), user.getEmail(), user.getOrganisation(), user.getKind(), user.getPhone(), LDAPConstants.LDAP_FLAG); } else { // Update existing LDAP user - + // Retrieve user id Element nextIdRequest = dbms.select("SELECT id FROM Users WHERE username = ?", user.getUsername()); id = nextIdRequest.getChild("record").getChildText("id"); @@ -83,14 +86,14 @@ static void saveUser(LDAPUser user, Dbms dbms, SerialFactory serialFactory, bool if (Log.isDebugEnabled(Geonet.LDAP)){ Log.debug(Geonet.LDAP, " - Update LDAP user " + user.getUsername() + " (" + id + ") in local database."); } - + // User update String query = "UPDATE Users SET username=?, password=?, surname=?, name=?, profile=?, address=?,"+ " city=?, state=?, zip=?, country=?, email=?, organisation=?, kind=?, phone=? WHERE id=?"; - dbms.execute (query, user.getUsername(), "", user.getSurname(), user.getName(), - user.getProfile(), user.getAddress(), user.getCity(), user.getState(), user.getZip(), + dbms.execute (query, user.getUsername(), "", user.getSurname(), user.getName(), + user.getProfile(), user.getAddress(), user.getCity(), user.getState(), user.getZip(), user.getCountry(), user.getEmail(), user.getOrganisation(), user.getKind(), user.getPhone(), new Integer(id)); - + // Delete user groups if (importPrivilegesFromLdap) { dbms.execute("DELETE FROM UserGroups WHERE userId=?", Integer.valueOf(id)); @@ -100,51 +103,56 @@ static void saveUser(LDAPUser user, Dbms dbms, SerialFactory serialFactory, bool // Add user groups if (importPrivilegesFromLdap && !Profile.ADMINISTRATOR.equals(user.getProfile())) { dbms.execute("DELETE FROM UserGroups WHERE userId=?", Integer.valueOf(id)); + Savepoint sp = null; for(Map.Entry privilege : user.getPrivileges().entries()) { - // Add group privileges for each groups - - // Retrieve group id - String groupName = privilege.getKey(); - String profile = privilege.getValue(); - - Element groupIdRequest = dbms.select("SELECT id FROM Groups WHERE name = ?", groupName); - Element groupRecord = groupIdRequest.getChild("record"); - String groupId = null; - - if (groupRecord == null && createNonExistingLdapGroup) { - createIfNotExist(groupName, groupId, dbms, serialFactory); - } - else if (groupRecord != null) { - groupId = groupRecord.getChildText("id"); - } - - if (createNonExistingLdapGroup) { - if (Log.isDebugEnabled(Geonet.LDAP)){ - Log.debug(Geonet.LDAP, " - Add LDAP group " + groupName + " for user."); - } - - Update.addGroup(dbms, Integer.valueOf(id), Integer.valueOf(groupId), profile); - - try { - if (profile.equals(Profile.REVIEWER)) { - Update.addGroup(dbms, Integer.valueOf(id), Integer.valueOf( - groupId), Profile.EDITOR); - } - } catch (Exception e) { - Log.debug(Geonet.LDAP, - " - User is already editor for that group." - + e.getMessage()); - } - } else { - if (Log.isDebugEnabled(Geonet.LDAP)){ - Log.debug(Geonet.LDAP, " - Can't create LDAP group " + groupName + " for user. " + - "Group does not exist in local database or createNonExistingLdapGroup is set to false."); - } - } - } + try { + sp = dbms.setSavePoint(); + // Add group privileges for each groups + + // Retrieve group id + String groupName = privilege.getKey(); + String profile = privilege.getValue(); + + Element groupIdRequest = dbms.select("SELECT id FROM Groups WHERE name = ?", groupName); + Element groupRecord = groupIdRequest.getChild("record"); + String groupId = null; + + if (groupRecord == null && createNonExistingLdapGroup) { + groupId = createIfNotExist(groupName, groupId, dbms, serialFactory); + } else if (groupRecord != null) { + groupId = groupRecord.getChildText("id"); + } + + if (createNonExistingLdapGroup) { + if (Log.isDebugEnabled(Geonet.LDAP)) { + Log.debug(Geonet.LDAP, " - Add LDAP group " + groupName + " for user."); + } + + Update.addGroup(dbms, Integer.valueOf(id), Integer.valueOf(groupId), profile); + + try { + if (profile.equals(Profile.REVIEWER)) { + Update.addGroup(dbms, Integer.valueOf(id), Integer.valueOf(groupId), Profile.EDITOR); + } + } catch (Exception e) { + Log.debug(Geonet.LDAP, " - User is already editor for that group." + e.getMessage()); + } + } else { + if (Log.isDebugEnabled(Geonet.LDAP)) { + Log.debug( + Geonet.LDAP, " - Can't create LDAP group " + groupName + " for user. " + + "Group does not exist in local database or createNonExistingLdapGroup is set to false."); + } + } + } catch (SQLException e) { + if (sp != null) { + dbms.rollbackToSavepoint(sp); + } + } + } // for() } user.setId(id); - + dbms.commit(); } @@ -156,16 +164,22 @@ else if (groupRecord != null) { * @param serialFactory * @throws SQLException */ - protected static void createIfNotExist(String groupName, String groupId, Dbms dbms, SerialFactory serialFactory) throws SQLException { + protected static String createIfNotExist(String groupName, String groupId, Dbms dbms, SerialFactory serialFactory) throws SQLException { if (Log.isDebugEnabled(Geonet.LDAP)){ Log.debug(Geonet.LDAP, " - Add non existing group '" + groupName + "' in local database."); } // If LDAP group does not exist in local database, create it + + // TODO: why passing groupId as argument if it is not actually used ? + // For now, we consider the LDAP server as authoritative, and the group name + // will be used. groupId = serialFactory.getSerial(dbms, "Groups") + ""; - String query = "INSERT INTO GROUPS(id, name) VALUES(?,?)"; - dbms.execute(query, Integer.valueOf(groupId), groupName); + String query = "INSERT INTO GROUPS(id, name, description) VALUES(?,?,?)"; + dbms.execute(query, Integer.valueOf(groupId), groupName, groupName); Lib.local.insert(dbms, "Groups", Integer.valueOf(groupId), groupName); + + return groupId; } static Map> convertAttributes( @@ -175,16 +189,16 @@ static Map> convertAttributes( while (attributesEnumeration.hasMore()) { Attribute attr = attributesEnumeration.next(); String id = attr.getID(); - + ArrayList values = userInfo.get(id); if (values == null) { values = new ArrayList(); userInfo.put(id, values); } - + // --- loop on all attribute's values NamingEnumeration valueEnum = attr.getAll(); - + while (valueEnum.hasMore()) { Object value = valueEnum.next(); // Only retrieve String attribute