/* * Data Hub Service (DHuS) - For Space data distribution. * Copyright (C) 2013,2014,2015 GAEL Systems * * This file is part of DHuS software sources. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package fr.gael.dhus.database.dao; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.SQLQuery; import org.hibernate.Session; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.support.DataAccessUtils; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.stereotype.Repository; import fr.gael.dhus.database.dao.interfaces.DaoEvent; import fr.gael.dhus.database.dao.interfaces.DaoListener; import fr.gael.dhus.database.dao.interfaces.DaoUtils; import fr.gael.dhus.database.dao.interfaces.HibernateDao; import fr.gael.dhus.database.dao.interfaces.UserListener; import fr.gael.dhus.database.object.Collection; import fr.gael.dhus.database.object.FileScanner; import fr.gael.dhus.database.object.Preference; import fr.gael.dhus.database.object.Role; import fr.gael.dhus.database.object.Search; import fr.gael.dhus.database.object.User; import fr.gael.dhus.database.object.restriction.AccessRestriction; import fr.gael.dhus.database.object.restriction.LockedAccessRestriction; import fr.gael.dhus.database.object.restriction.TmpUserLockedAccessRestriction; import fr.gael.dhus.server.ScalabilityManager; import fr.gael.dhus.service.exception.UserAlreadyExistingException; import fr.gael.dhus.spring.context.ApplicationContextProvider; import fr.gael.dhus.system.config.ConfigurationManager; @Repository public class UserDao extends HibernateDao<User, String> { @Autowired private CollectionDao collectionDao; @Autowired private ConfigurationManager cfgManager; @Autowired private ProductCartDao productCartDao; @Autowired private SearchDao searchDao; @Autowired private AccessRestrictionDao accessRestrictionDao; @Autowired private FileScannerDao fileScannerDao; @Autowired private ScalabilityManager scalabilityManager; /** * Unique public data user. */ private User publicData; /** * Public data username. */ private final String publicDataName = "public data"; public User getByName (final String name) { User user = (User)DataAccessUtils.uniqueResult ( getHibernateTemplate ().find ( "From User u where u.username=?", name)); // Optimization user extraction: most of the users uses case-sensitive // match for the login. A Requirement of the project asked for non-case // sensitive match. The extraction of non-case sensitive login from // database requires conversions and forbid the usage of indexes, so it // is much more slow. // This Fix aims to first try the extraction of the user with exact match // equals operator, then if not match use the toLower conversion. if (user==null) user = (User)DataAccessUtils.uniqueResult ( getHibernateTemplate ().find ( "From User u where lower(u.username)=lower(?)", name)); return user; } @Override public void delete (final User user) { if (user == null) return; // remove user external references final String uid = user.getUUID (); productCartDao.deleteCartOfUser (user); getHibernateTemplate ().execute (new HibernateCallback<Void> () { @Override public Void doInHibernate (Session session) throws HibernateException, SQLException { String sql = "DELETE FROM COLLECTION_USER_AUTH WHERE USERS_UUID = :uid"; SQLQuery query = session.createSQLQuery (sql); query.setString ("uid", uid); query.executeUpdate (); return null; } }); getHibernateTemplate ().execute (new HibernateCallback<Void> () { @Override public Void doInHibernate (Session session) throws HibernateException, SQLException { String sql = "DELETE FROM PRODUCT_USER_AUTH WHERE USERS_UUID = :uid"; SQLQuery query = session.createSQLQuery (sql); query.setString ("uid", uid); query.executeUpdate (); return null; } }); getHibernateTemplate ().execute (new HibernateCallback<Void> () { @Override public Void doInHibernate (Session session) throws HibernateException, SQLException { String sql = "UPDATE PRODUCTS SET OWNER_UUID = NULL " + "WHERE OWNER_UUID = :uid"; SQLQuery query = session.createSQLQuery (sql); query.setString ("uid", uid); query.executeUpdate (); return null; } }); getHibernateTemplate ().execute (new HibernateCallback<Void> () { @Override public Void doInHibernate (Session session) throws HibernateException, SQLException { String sql = "DELETE FROM NETWORK_USAGE WHERE USER_UUID = :uid"; SQLQuery query = session.createSQLQuery (sql); query.setString ("uid", uid); query.executeUpdate (); return null; } }); fireDeletedEvent (new DaoEvent<User> (user)); super.delete (user); } public void removeUser (User user) { user.setDeleted (true); getHibernateTemplate ().update (user); productCartDao.deleteCartOfUser(user); try { fireDeletedEvent(new DaoEvent<User>(user)); } catch (Exception ex) { logger.error("Exception occured in listener", ex); } } private void forceDelete (User user) { super.delete (read (user.getUUID ())); } @SuppressWarnings ("unchecked") public List<User> scrollNotDeleted (final int skip, final int top) { // FIXME never call return getHibernateTemplate ().execute ( new HibernateCallback<List<User>> () { @Override public List<User> doInHibernate (Session session) throws HibernateException, SQLException { String hql = "FROM User WHERE deleted = false AND not username = " + "'" + cfgManager.getAdministratorConfiguration () .getName () + " AND not username = '" + getPublicData ().getUsername () + "'" + " ORDER BY username"; Query query = session.createQuery (hql).setReadOnly (true); query.setFirstResult (skip); query.setMaxResults (top); return (List<User>) query.list (); } }); } public Iterator<User> scrollForDataRight() { String filter = "WHERE deleted is false AND (not username = '" + cfgManager.getAdministratorConfiguration ().getName () + "' ORDER BY username"; String query = "FROM " + entityClass.getName () + " " + filter; return new PagedIterator<User> (this, query); } @SuppressWarnings ("unchecked") public List<User> readNotDeleted () { return (List<User>)find ( "FROM " + entityClass.getName () + " u WHERE u.deleted is false and " + "not u.username='" + cfgManager.getAdministratorConfiguration ().getName () + "' " + "and not u.username LIKE '"+ getPublicData ().getUsername () + "' " + "order by username"); } public Iterator<User> scrollNotDeleted (final String filter, int skip) { StringBuilder query = new StringBuilder (); query.append ("FROM ").append (entityClass.getName ()).append (" "); query.append ("WHERE deleted is false AND ") .append ("username LIKE'%").append (filter).append ("%' AND "); query.append ("not username='").append (getRootUser ().getUsername ()) .append ("' AND not username='").append (getPublicDataName ()) .append ("' "); query.append ("ORDER BY username"); return new PagedIterator<> (this, query.toString (), skip, 3); } public Iterator<User> scrollForDataRight (String filter, int skip) { StringBuilder query = new StringBuilder (); query.append ("FROM ").append (entityClass.getName ()).append (" "); query.append ("WHERE deleted is false AND username LIKE '%") .append (filter).append ("%' AND not username='") .append (cfgManager.getAdministratorConfiguration ().getName ()) .append ("' "); query.append ("ORDER BY CASE username WHEN '").append (getPublicDataName ()) .append ("' THEN 1 ELSE 2 END, username"); return new PagedIterator<> (this, query.toString (), skip); } public Iterator<User> scrollAll (String filter, int skip) { StringBuilder query = new StringBuilder (); query.append ("FROM ").append (entityClass.getName ()).append (" "); query.append ("WHERE username LIKE '%").append (filter).append ("%' "); query.append ("AND not username='") .append (getPublicData ().getUsername ()).append ("' "); query.append ("ORDER BY username"); return new PagedIterator<> (this, query.toString (), skip); } public int countNotDeleted (String filter) { return DataAccessUtils.intResult (find ( "select count(*) FROM " + entityClass.getName () + " u WHERE u.deleted is false AND u.username LIKE '%" + filter + "%' and " + "not u.username='" + cfgManager.getAdministratorConfiguration ().getName () + "'" + " and not u.username LIKE '"+getPublicData ().getUsername ()+"' ")); } public int countForDataRight (String filter) { return DataAccessUtils.intResult (find ( "select count(*) FROM " + entityClass.getName () + " u WHERE u.deleted is false AND u.username LIKE '%" + filter + "%' and " + "not u.username='" + cfgManager.getAdministratorConfiguration ().getName () + "' ")); } public int countAll (String filter) { return DataAccessUtils.intResult (find ( "select count(*) FROM " + entityClass.getName () + " u WHERE u.username LIKE '%" + filter + "%'" + " and not u.username LIKE '" + getPublicData ().getUsername () + "' " )); } public void addAccessToCollection (User user, Collection collection) { List<User> users = collectionDao.getAuthorizedUsers (collection); // Check is already granted for (User u : users) { if (u.getUUID ().equals (user.getUUID())) { return; } } users.add (user); collection.setAuthorizedUsers (new HashSet<User> (users)); collectionDao.update (collection); } public void removeAccessToCollection (String user_uuid, Collection collection) { // if data are public, not possible to remove user right. if (cfgManager.isDataPublic ()) { return; } List<User> users = collectionDao.getAuthorizedUsers (collection); // Check is already granted User selection = null; for (User u : users) { if (u.getUUID ().equals (user_uuid)) { selection = u; } } if (selection != null) { users.remove (selection); collection.setAuthorizedUsers (new HashSet<User> (users)); collectionDao.update (collection); } } public String computeUserCode (User user) { if (user == null) throw new NullPointerException ("Null user."); if (user.getUUID () == null) throw new IllegalArgumentException ("User " + user.getUsername () + " must be created in the DB to compute its code."); String digest = user.hash (); String code = user.getUUID () + digest; return code; } public User getUserFromUserCode (String code) { if (code == null) throw new NullPointerException ("Null code."); String id = code.substring (0, 36); // Retrieve the user User user = read (id); if (user == null) throw new NullPointerException ("User cannot be retrieved for id " + id); // Check the Id String hash = user.hash (); String user_hash = code.substring (36); if ( !hash.equals (user_hash)) throw new SecurityException ("Wrong hash code \"" + user_hash + "\"."); return user; } public void lockUser (User user, String reason) { LockedAccessRestriction ar = new LockedAccessRestriction (); ar.setBlockingReason (reason); user.addRestriction (ar); update (user); } public void unlockUser (User user, Class<? extends AccessRestriction> car) { if (user.getRestrictions () == null) return; Iterator<AccessRestriction> iter = user.getRestrictions ().iterator (); HashSet<AccessRestriction> toDelete = new HashSet<AccessRestriction> (); while (iter.hasNext ()) { AccessRestriction lar = iter.next (); if (lar.getClass ().equals (car)) { iter.remove (); toDelete.add (lar); } } update (user); for (AccessRestriction restriction : toDelete) { accessRestrictionDao.delete (restriction); } } /** * Create a temporary user. * * @param temporary user. * @return the updated user. */ public void createTmpUser (User user) { TmpUserLockedAccessRestriction tuar = new TmpUserLockedAccessRestriction (); user.addRestriction (tuar); create (user); } @Override public User create (User u) { User user = getByName (u.getUsername ()); if (user != null) { throw new UserAlreadyExistingException ( "An user is already registered with name '" + u.getUsername () + "'."); } // Default new user come with at least search access role. if (u.getRoles ().isEmpty ()) { u.addRole (Role.SEARCH); u.addRole (Role.DOWNLOAD); } return super.create (u); } public void registerTmpUser (User u) { unlockUser (u, TmpUserLockedAccessRestriction.class); fireUserRegister (new DaoEvent<User> (u)); } public boolean isTmpUser (User u) { if (u.getRestrictions () == null) { return false; } for (AccessRestriction ar : u.getRestrictions ()) { if (ar instanceof TmpUserLockedAccessRestriction) { return true; } } return false; } public void cleanupTmpUser (int max_days) { int skip = 0; final int top = DaoUtils.DEFAULT_ELEMENTS_PER_PAGE; long MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24; long runtime = System.currentTimeMillis (); final String hql = "SELECT u, r FROM User u LEFT OUTER JOIN " + "u.restrictions r WHERE r.discriminator = 'temporary'"; List<Object[]> result; HibernateTemplate template = getHibernateTemplate (); do { final int start = skip; result = template.execute (new HibernateCallback<List<Object[]>>() { @Override @SuppressWarnings ("unchecked") public List<Object[]> doInHibernate (Session session) throws HibernateException, SQLException { Query query = session.createQuery (hql).setReadOnly (true); query.setFirstResult (start); query.setMaxResults (top); return (List<Object[]>) query.list (); } }); for (Object[] objects : result) { if (objects.length != 2) continue; User user = User.class.cast (objects[0]); TmpUserLockedAccessRestriction restriction = TmpUserLockedAccessRestriction.class.cast (objects[1]); long date = runtime - restriction.getLockDate ().getTime (); if ((date / MILLISECONDS_PER_DAY) >= max_days) { logger.info("Remove unregistered User " + user.getUsername ()); forceDelete (user); } } skip = skip + top; } while (result.size () == top); } public User getRootUser() { return getByName (cfgManager.getAdministratorConfiguration ().getName ()); } public boolean isRootUser (User user) { if (user.getUsername ().equals ( cfgManager.getAdministratorConfiguration ().getName ())) return true; return false; } void fireUserRegister (DaoEvent<User> e) { for (DaoListener<?> listener : getListeners ()) { if (listener instanceof UserListener) ((UserListener) listener).register (e); } } // Preference settings private void updateUserPreference (User user) { getHibernateTemplate ().update (user); } public void storeUserSearch (User user, String request, String footprint, HashMap<String, String> advanced, String complete) { Preference pref = user.getPreferences (); Search search = new Search(); search.setValue (request); search.setFootprint (footprint); search.setAdvanced (advanced); search.setComplete (complete); search.setNotify (false); search = searchDao.create (search); pref.getSearches ().add (search); updateUserPreference (user); } public void removeUserSearch (User user, String uuid) { Search search = searchDao.read(uuid); if (search != null) { Preference pref = user.getPreferences (); Set<Search> s = pref.getSearches (); Iterator<Search> iterator = s.iterator (); while (iterator.hasNext ()) { if (iterator.next ().equals (search)) { iterator.remove (); } } updateUserPreference (user); } searchDao.delete (search); } public void activateUserSearchNotification (String uuid, boolean notify) { Search search = searchDao.read (uuid); search.setNotify (notify); searchDao.update (search); } public void clearUserSearches (User user) { Preference pref = user.getPreferences (); pref.getSearches ().clear (); updateUserPreference (user); } public List<Search> getUserSearches (User user) { Set<Search> searches = read (user.getUUID ()).getPreferences (). getSearches (); List<Search> list = new ArrayList<Search> (searches); Collections.sort (list, new Comparator<Search> () { @Override public int compare (Search arg0, Search arg1) { return arg0.getValue ().compareTo (arg1.getValue ()); } }); return list; } // File Scanner preferences /** * Add a file scanner in user preferences, if it already exists, it is * updated otherwise, it is created and added. */ public FileScanner addFileScanner (User user, String url, String username, String password, String pattern, String cron_schedule, Set<Collection> collections) { FileScanner fs = null; boolean create = false; // if ( (fs = findFileScanner (user, url, username)) == null) // { fs = new FileScanner (); create = true; // } fs.setUrl (url); fs.setUsername (username); fs.setPassword (password); fs.setPattern (pattern); fs.setStatus (FileScanner.STATUS_ADDED); SimpleDateFormat sdf = new SimpleDateFormat ( "EEEE dd MMMM yyyy - HH:mm:ss", Locale.ENGLISH); fs.setStatusMessage ("Added on "+sdf.format(new Date ())); fs.setCollections (collections); fs.setCronSchedule (cron_schedule); // Create and retrieve the fs ibnstance in DB; if (create) { fileScannerDao.create (fs); UserDao userDao = ApplicationContextProvider.getBean (UserDao.class); user = userDao.read (user.getUUID ()); user.getPreferences ().getFileScanners ().add (fs); updateUserPreference (user); } else { fileScannerDao.update (fs); } return fs; } public void updateFileScanner (Long scan_id, String url, String username, String password, String pattern, String cron_schedule, Set<Collection> collections) { FileScanner fs = fileScannerDao.read (scan_id); fs.setUrl (url); fs.setUsername (username); fs.setPassword (password); fs.setPattern (pattern); fs.setStatus (FileScanner.STATUS_ADDED); SimpleDateFormat sdf = new SimpleDateFormat ( "EEEE dd MMMM yyyy - HH:mm:ss", Locale.ENGLISH); fs.setStatusMessage ("Updated on " + sdf.format (new Date ())); fs.setCollections (collections); fs.setCronSchedule (cron_schedule); fileScannerDao.update (fs); } public void removeFileScanner (User user, Long scanner_id) { FileScanner fs = fileScannerDao.read (scanner_id); if (fs != null) { fileScannerDao.delete (fs); getHibernateTemplate ().refresh (user); user.getPreferences ().getFileScanners ().remove (fs); updateUserPreference (user); } } public void setFileScannerActive (Long id, boolean active) { FileScanner fs = fileScannerDao.read (id); fs.setActive (active); fileScannerDao.update (fs); } public FileScanner findFileScanner (User user, String url, String username) { Set<FileScanner> fss = getFileScanners (user); for (FileScanner fs : fss) { /** * URL in not case sensitive instead of username is for ftp */ if (url.equalsIgnoreCase (fs.getUrl ()) && username.equals (fs.getUsername ())) { return fs; } } return null; } public Set<FileScanner> getFileScanners (User user) { return read (user.getUUID ()).getPreferences ().getFileScanners (); } public User getPublicData () { if (publicData != null) { return publicData; } publicData = getByName (getPublicDataName ()); if (publicData == null && (!scalabilityManager.isActive () || scalabilityManager.isMaster ())) { createPublicData (); } return publicData; } private void createPublicData () { publicData = new User(); publicData.setUsername (getPublicDataName ()); publicData.setPassword ("#"); publicData.setCreated (new Date ()); publicData = create(publicData); } public String getPublicDataName () { return "~" + publicDataName; } /** * Get not deleted users for the given filter, offset and limit * @param filter * @param offset * @param limit * @return */ public Iterator<User> scrollNotDeletedByFilter (String filter, int skip) { String s = filter.toLowerCase (); StringBuilder sb = new StringBuilder (); sb.append ("FROM ").append (entityClass.getName ()).append (" "); sb.append ("WHERE deleted is false AND "); sb.append ("(username LIKE '%").append (s).append ("%' ") .append ("OR lower(firstname) LIKE '%").append (s).append ("%' ") .append ("OR lower(lastname) LIKE '%").append (s).append ("%' ") .append ("OR lower(email) LIKE '%").append (s).append ("%') "); sb.append ("AND not username='") .append (cfgManager.getAdministratorConfiguration ().getName ()) .append ("' AND not username='").append (getPublicDataName ()) .append ("' "); sb.append ("ORDER BY username"); return new PagedIterator<> (this, sb.toString (), skip); } public int countNotDeletedByFilter (String filter) { return DataAccessUtils.intResult (find ( "select count(*) FROM " + entityClass.getName () + " u WHERE u.deleted is false AND (u.username LIKE '%" + filter + "%' OR lower(u.firstname) LIKE '%"+filter.toLowerCase()+ "%' OR lower(u.lastname) LIKE '%"+filter.toLowerCase()+ "%' OR lower(u.email) LIKE '%"+filter.toLowerCase()+ "%') and not u.username='" + cfgManager.getAdministratorConfiguration ().getName () + "'" + " and not u.username LIKE '"+getPublicData ().getUsername ()+"' ")); } /** * Retrieve the users list by (top,skip) page according to the passed filter * with configurable ordering. * @param filter the filter to run, if null, all the users will be returned. * @param order_by the order sequence, if null, default database order will be returned. * @param skip elements number to skip in the list. * @param top element kept in the list. * @return the list of filtered users. */ public List<User> getUsers (String filter, String order_by, final int skip, final int top) { // TODO Security on filter & orderBy string StringBuilder qBuilder = new StringBuilder (); // Scroll already add FROM entity class // qBuilder.append ("FROM User u "); // Just add the entity referer qBuilder.append (" u "); if (filter != null && !filter.isEmpty ()) { qBuilder.append ("WHERE "); qBuilder.append (filter); } // Builds the ORDER BY clause. if (order_by != null && !order_by.isEmpty ()) { qBuilder.append (" ORDER BY "); qBuilder.append (order_by); } String hql = qBuilder.toString (); return scroll (hql, skip, top); } /** * Count the user according to the passed filter. * @param filter filter over users. If null all the users will be returned. * @return number of users. */ public int countUsers (String filter) { // TODO Security on filter string StringBuilder qBuilder = new StringBuilder (); qBuilder.append ("SELECT count (*) FROM User u "); if (filter != null && !filter.isEmpty ()) { qBuilder.append ("WHERE "); qBuilder.append (filter); } return ((Long) getHibernateTemplate ().find (qBuilder.toString ()) .get (0)).intValue (); } public Iterator<User> getAllUsers () { return new PagedIterator<> (this, "FROM " + entityClass.getName ()); } }