/* Copyright (c) 2012-2014, terrestris GmbH & Co. KG
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* (This is the BSD 3-Clause, sometimes called 'BSD New' or 'BSD Simplified',
* see http://opensource.org/licenses/BSD-3-Clause)
*/
package de.terrestris.shogun.service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.hibernate.criterion.Restrictions;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import de.terrestris.shogun.exception.ShogunDatabaseAccessException;
import de.terrestris.shogun.exception.ShogunServiceException;
import de.terrestris.shogun.model.Group;
import de.terrestris.shogun.model.MapConfig;
import de.terrestris.shogun.model.MapLayer;
import de.terrestris.shogun.model.Module;
import de.terrestris.shogun.model.User;
import de.terrestris.shogun.model.WfsProxyConfig;
import de.terrestris.shogun.model.WmsProxyConfig;
import de.terrestris.shogun.util.Mail;
import de.terrestris.shogun.util.Password;
/**
* A service class of SHOGun offering user related business logic.
*
* @author terrestris GmbH & Co. KG
*
*/
@Service
public class UserAdministrationService extends AbstractShogunService {
/**
*
*/
private static Logger LOGGER = Logger.getLogger(UserAdministrationService.class);
/**
* User defined by the given userId gets a new password. Password is
* generated by random and will be sent to the User via email.
*
* @param userId the ID of the {@link User} object in the database
* @throws ShogunServiceException If an error arises
*/
@Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public void updateUserPassword(String userId) throws ShogunServiceException {
User user = null;
try {
// create an integer out of the id-String
int iUserId = Integer.parseInt(userId);
// get the User object
user = (User) this.getDatabaseDao().getEntityById(iUserId, User.class);
} catch (Exception e) {
throw new ShogunServiceException(
"Error while getting User from database with ID " +
userId + " " + e.getMessage());
}
try {
// set the new password
String newPassword = Password.getRandomPassword(8);
PasswordEncoder pwencoder = new Md5PasswordEncoder();
String hashed = pwencoder.encodePassword(newPassword, null);
user.setUser_password(hashed);
// write back to database
this.getDatabaseDao().updateUser(user);
// send an email with the new password
//TODO remove static texts
//TODO remove static mail server settings
String mailtext = "Sehr geehrter Nutzer " + user.getUser_name()
+ "\n\n";
mailtext += "Ihr SHOGun-Passwort wurde geändert und lautet \n\n";
mailtext += newPassword + "\n\n";
Mail.send("localhost", 25, user.getUser_email(),
"admin", "Passwort-Änderung bei SHOGun", mailtext);
} catch (Exception e) {
throw new ShogunServiceException(
"Error while updating User in database or while " +
"sending email. " + e.getMessage());
}
}
/**
*
* Inserts new {@link User} objects into the database. The new objects are
* send to the DAO to save them into the DB. <br>
* It is checked if the corresponding group has no other User with the same name. <br>
* Before saving the User object:
* <ul>
* <li>a random password is generated and save in the User instance</li>
* <li>new password is sent to the user via email</li>
* <li>create a {@link Module} object list from the comma-separated list of
* module IDs (user_module_list)</li>
* </ul>
*
* <b>CAUTION: Only if the logged in user has the role ROLE_ADMIN the
* function is accessible, otherwise access is denied.</b>
*
* @param newUsers
* a list of {@link User} objects to be inserted
* @return a list of {@link User} objects which have been inserted
* @throws ShogunDatabaseAccessException
* @throws ShogunServiceException
* @throws Exception
* if a user with the same name
* already exists
*/
@Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public List<User> createUsers(List<User> newUsers) throws ShogunDatabaseAccessException, ShogunServiceException {
List<User> returnUsers = new ArrayList<User>();
// iterate over user delivered by client request
for (Iterator<User> iterator = newUsers.iterator(); iterator.hasNext();) {
User user = iterator.next();
User newuser = null;
// CREATE A NEW USER
// check if there is an existing user with the same name
// if there is --> ERROR
List<User> testUser = this.getDatabaseDao().getUserByName(user.getUser_name());
if (testUser.size() > 0) {
throw new ShogunServiceException(
"User with name " + user.getUser_name()
+ " already exists!");
}
// create a new random password and send it per mail to new user
String pw = Password.getRandomPassword(8);
PasswordEncoder pwencoder = new Md5PasswordEncoder();
String hashed = pwencoder.encodePassword(pw, null);
user.setUser_password(hashed);
// TODO become more flexibel here, wrap to method
// set the default map conf
// TODO remove static ID !!!!!
MapConfig mapConfig =
(MapConfig)this.getDatabaseDao().getEntityById(1, MapConfig.class);
if (mapConfig != null) {
// Set newMapConfSet = new HashSet<MapConfig>();
// newMapConfSet.add(mapConfig);
// user.setMapConfigs(newMapConfSet);
user.setMapConfig(mapConfig);
}
// TODO become more flexibel here, wrap to method
// set the default wms proxy conf
// TODO remove static ID !!!!!
WmsProxyConfig defaultWmsProxy =
(WmsProxyConfig)this.getDatabaseDao().getEntityById(1, WmsProxyConfig.class);
if (defaultWmsProxy != null) {
user.setWmsProxyConfig(defaultWmsProxy);
}
// TODO become more flexibel here, wrap to method
// set the default wms proxy conf
// TODO remove static ID !!!!!
WfsProxyConfig defaultWfsProxy =
(WfsProxyConfig)this.getDatabaseDao().getEntityById(1, WfsProxyConfig.class);
if (defaultWfsProxy != null) {
user.setWfsProxyConfig(defaultWfsProxy);
}
//TODO remove this static mail text
String mailtext = "Sehr geehrter Nutzer " + user.getUser_name()
+ "\n\n";
mailtext += "Ihr SHOGun-Passwort lautet \n\n";
mailtext += pw + "\n\n";
Mail.send("localhost", 25, user.getUser_email(), "admin", "Registrierung bei SHOGun", mailtext);
// write in DB
// do setSessionGroup only in case of beeing NO SuperAdmin
newuser = this.getDatabaseDao().createUser(user, true);
LOGGER.debug(" USER RETURNED: " + newuser.getId());
returnUsers.add(newuser);
}
return returnUsers;
}
/**
* Updates User objects in the database. The changed objects are send to the
* DAO to save them into the DB. <br>
* <b>CAUTION: Only if the logged in user has the role ROLE_ADMIN the
* function is accessible, otherwise access is denied.</b>
*
* @param updatedUsers a list of {@link User} objects to be updated.
* @return a list of {@link User} objects which have been updated
* @throws ShogunDatabaseAccessException
* @throws ShogunServiceException
*
*/
@Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public List<User> updateUser(List<User> updatedUsers) throws ShogunDatabaseAccessException, ShogunServiceException {
List<User> returnUsers = new ArrayList<User>();
// iterate over user delivered by client request
for (Iterator<User> iterator = updatedUsers.iterator(); iterator.hasNext();) {
// user to be updated
User user = iterator.next();
// Check if logged-in user has the same group than the
// user to be updated
List<Integer> groupsOfSessionUser = this.getDatabaseDao().getGroupIdsFromSession();
User oldUser = this.getDatabaseDao().getUserByName(
user.getUser_name(),
"groups",
Restrictions.in("id", groupsOfSessionUser));
// no user found in own groups --> exception
if (oldUser == null) {
throw new ShogunServiceException(
"The User to be updated is not accessible for the " +
"logged in user!");
}
// if password is empty/null in request from client, keep the old
// one
if (user.getUser_password() == null) {
user.setUser_password(oldUser.getUser_password());
}
// TODO make this configurable
// if wfs/wms conf is empty in request from client, keep the old
// one
if (user.getWfsProxyConfig() == null) {
user.setWfsProxyConfig(oldUser.getWfsProxyConfig());
}
if (user.getWmsProxyConfig() == null) {
user.setWmsProxyConfig(oldUser.getWmsProxyConfig());
}
// if map conf is empty in request from client, keep the old one
if (user.getMapConfig() == null) {
user.setMapConfig(oldUser.getMapConfig());
}
// if groups is empty in request from client, keep the old one
if (user.getGroups() == null) {
user.setGroups(oldUser.getGroups());
}
// write in DB
User updatedUser = this.getDatabaseDao().updateUser(user);
returnUsers.add(updatedUser);
// clear the session cache in order to have to current updated
// objects
// when requesting again
// unfortunately sess.evict does not work
this.getDatabaseDao().clearSession();
}
return returnUsers;
}
/**
* Delete a {@link User} object, defined by its ID, from the database
*
* @param deleteId
* @throws ShogunDatabaseAccessException
* @throws ShogunServiceException
*/
@Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public void deleteUser(Integer deleteId)
throws ShogunServiceException, ShogunDatabaseAccessException {
List<Integer> sessionUserGroups = this.getDatabaseDao().getGroupIdsFromSession();
// get the possible user object
User userToDelete = null;
// ROLE_SUPERADMIN can delete every user
// ROLE_ADMIN only those who are in his group
if (this.getDatabaseDao().isSuperAdmin()) {
userToDelete = (User) this.getDatabaseDao().getEntityById(deleteId, User.class);
} else {
userToDelete = this.getDatabaseDao().getUserById(
deleteId, "groups", Restrictions.in("id", sessionUserGroups));
}
// check if user for deletion exists
if (userToDelete == null) {
throw new ShogunServiceException(
"No user with ID " + deleteId + " found for deletion");
}
// remove all group relationships to prevent
// referential integrity constraint violation
Set<Group> userGroups = userToDelete.getGroups();
for (Group group : userGroups) {
group.getUsers().remove(userToDelete);
}
// delete in database
this.getDatabaseDao().deleteEntity(User.class, userToDelete);
}
/**
* Returns a list of all active users from database.
*
* @return list of active {@link User} objects
*/
@Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public List<User> getActiveUsers() {
return this.getDatabaseDao().getEntitiesByBooleanField(User.class, "active", true);
}
/**
* Inserts a new {@link Group} object into the database. <br>
* It is checked if there is no other Group with the same number. <br>
* Before saving the Group object:
* <ul>
* <li>create a {@link Module} object list from the comma-separated list of
* module IDs (user_module_list)</li>
* <li>a sub-admin as {@link User} instance is created with the base values
* of the Group instance (street, etc.) in case of triggering this method
* as SUPER_ADMIN</li>
* <li>a random password is generated and save in the sub-admin instance</li>
* <li>the new password is sent to the sub-admin via email</li>
* </ul>
*
* <b>CAUTION: Only if the logged in user has the role ROLE_ADMIN or
* ROLE_SUPERADMIN the
* function is accessible, otherwise access is denied.</b>
*
* @param group a {@link Group} object to be inserted
* @return the {@link Group} object which has been inserted
* @throws ShogunServiceException if a Group with the same group_nr already exists
*/
@Transactional(rollbackFor = ShogunServiceException.class)
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public Group createGroup(Group group) throws ShogunServiceException, ShogunDatabaseAccessException {
Group newGroup = null;
// check if there is an existing user with the same number
// if there is --> Exception
Group testGroup = null;
testGroup = (Group) this.getDatabaseDao().getEntityByStringField(
Group.class, "group_nr", group.getGroup_nr());
if (testGroup != null) {
throw new ShogunServiceException(
"Group with number " + group.getGroup_nr()
+ " already exists!");
}
// transform the comma-separated list of module IDs to a list of
// Module objects
group.transformSimpleModuleListToModuleObjects(this.getDatabaseDao());
/*
* convert the transmitted user IDs to real user objects
*/
Set<Integer> userIds = group.getGrantedUsers();
if (userIds != null && userIds.size() > 0) {
Object[] aUserIds = userIds.toArray();
@SuppressWarnings("unchecked")
List<User> usersToGrantList = (List<User>) this.getDatabaseDao().getEntitiesByIds(aUserIds, User.class);
// convert to a set
Set<User> usersToGrantSet = new HashSet<User>(usersToGrantList);
group.setUsers(usersToGrantSet);
}
/*
* convert the transmitted MapLayer IDs to real MapLayer objects
*/
Set<Integer> mapLayers = group.getGrantedMapLayers();
if (mapLayers != null && mapLayers.size() > 0) {
Object[] aMapLayerIds = mapLayers.toArray();
@SuppressWarnings("unchecked")
List<MapLayer> wmsMapLayers = (List<MapLayer>) this
.getDatabaseDao().getEntitiesByIds(aMapLayerIds,
MapLayer.class);
Set<MapLayer> layers = new HashSet<MapLayer>(wmsMapLayers);
group.setMapLayers(layers);
}
// write new Group in DB
newGroup = (Group) this.getDatabaseDao().createEntity(
Group.class.getSimpleName(), group);
LOGGER.debug("Group created in database with ID: " + newGroup.getId());
// check if the logged in user has the ROLE_ADMIN,
// so we do not need to create an admin as group leader,
// the logged in User is the group leader
User sessionUser = this.getDatabaseDao().getUserObjectFromSession();
User persistentSubadmin = null;
if (sessionUser.hasAdminRole() == true) {
persistentSubadmin = sessionUser;
} else {
persistentSubadmin = this.createSubadminForGroup(newGroup);
}
// add sub-admin to the created group
newGroup.getUsers().add(persistentSubadmin);
this.getDatabaseDao().updateEntity("Group", newGroup);
return newGroup;
}
/**
* Updates a Group object in the database. <br><br>
*
* This method only updates the plain group information in the database.
*
* <b>CAUTION: Only if the logged in user has the role ROLE_ADMIN or
* the role ROLE_SUPERADMIN the
* function is accessible, otherwise access is denied.</b>
*
*
* @param groupToUpdate
* a {@link Group} object which should be updated
* @return the persistent {@link Group} object which have been successfully
* updated
* @throws ShogunDatabaseAccessException
* @throws ShogunServiceException
*/
@Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public Group updateGroup(Group groupToUpdate) throws ShogunDatabaseAccessException, ShogunServiceException {
User subadmin = null;
// check if the logged in user has the ROLE_ADMIN,
// so we have to check if has the right to update this group
User sessionUser = this.getDatabaseDao().getUserObjectFromSession();
if (sessionUser.hasAdminRole() == true) {
boolean isAllowed = false;
Set<Group> groupsOfSessionUser = sessionUser.getGroups();
for (Group group : groupsOfSessionUser) {
if (group.getId() == groupToUpdate.getId()) {
isAllowed = true;
}
}
if (isAllowed == false) {
throw new ShogunServiceException(
"Access denied: User not allowed to update this group");
}
// set the logged in user as subadmin
subadmin = sessionUser;
} else {
Group persistentGroup = (Group) this.getDatabaseDao().getEntityById(
groupToUpdate.getId(), Group.class);
// detect a sub-admin of this group, because the logged in user
// is the super-admin
Set<User> usersOfExistingGroup = persistentGroup.getUsers();
for (User user : usersOfExistingGroup) {
if (user.hasAdminRole()) {
subadmin = user;
break;
}
}
if (subadmin == null) {
throw new ShogunServiceException(
"No sub-admin found for given group.");
}
}
// transform the comma-separated list of module IDs to a list of
// Module objects
groupToUpdate.transformSimpleModuleListToModuleObjects(this.getDatabaseDao());
/*
* convert the transmitted user IDs to real user objects
*/
Set<Integer> userIds = groupToUpdate.getGrantedUsers();
if (userIds != null && userIds.size() > 0) {
Object[] aUserIds = userIds.toArray();
@SuppressWarnings("unchecked")
List<User> usersToGrantList = (List<User>) this.getDatabaseDao().getEntitiesByIds(aUserIds, User.class);
// convert to a set
Set<User> usersToGrantSet = new HashSet<User>(usersToGrantList);
groupToUpdate.setUsers(usersToGrantSet);
}
/*
* convert the transmitted MapLayer IDs to real MapLayer objects
*/
Set<Integer> mapLayers = groupToUpdate.getGrantedMapLayers();
if (mapLayers != null && mapLayers.size() > 0) {
Object[] aMapLayerIds = mapLayers.toArray();
@SuppressWarnings("unchecked")
List<MapLayer> wmsMapLayers = (List<MapLayer>) this
.getDatabaseDao().getEntitiesByIds(aMapLayerIds,
MapLayer.class);
Set<MapLayer> layers = new HashSet<MapLayer>(wmsMapLayers);
groupToUpdate.setMapLayers(layers);
}
// write in DB
Group updatedGroup = (Group) this.getDatabaseDao().updateEntity(
Group.class.getSimpleName(), groupToUpdate);
// update the sub-admin
this.getDatabaseDao().updateUser(subadmin);
return updatedGroup;
}
/**
* Deletes an Group object from database. Record is specified by its ID <br>
* <b>CAUTION: Only if the logged in user has the role ROLE_SUPERADMIN the
* function is accessible, otherwise access is denied.</b>
*
* @param deleteId
* the ID of the record to be deleted
* @throws ShogunDatabaseAccessException
* @throws ShogunServiceException
*/
@Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public void deleteGroup(Integer deleteId) throws ShogunServiceException {
// check if the logged in user has the ROLE_ADMIN,
// so we have to check if has the right to delete this group
User sessionUser = this.getDatabaseDao().getUserObjectFromSession();
if (sessionUser.hasAdminRole() == true) {
boolean isAllowed = false;
Set<Group> groupsOfSessionUser = sessionUser.getGroups();
for (Group group : groupsOfSessionUser) {
if (group.getId() == deleteId) {
isAllowed = true;
}
}
if (isAllowed == false) {
throw new ShogunServiceException(
"Access denied: User not allowed to delete this group");
}
}
this.getDatabaseDao().deleteEntity(Group.class, deleteId);
}
/**
* Returns all related groups for the logged in User.
* If the logged in user is a SUPERADMIN so all groups are returned.
* Otherwise only the own groups are returned.
*
* @return
*/
@Transactional
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public List<Group> getAllOwnedGroups() {
// check if the logged in user has the ROLE_SUPERADMIN,
// so we have to return all groups
User sessionUser = this.getDatabaseDao().getUserObjectFromSession();
List<Group> ownedGroups = null;
if (sessionUser.hasSuperAdminRole() == true) {
List<Object> allGroupsAsObject = this.getDatabaseDao().getAllEntities(Group.class);
ownedGroups = new ArrayList<Group>();
for (Object object : allGroupsAsObject) {
Group group = (Group) object;
ownedGroups.add(group);
}
} else {
ownedGroups = new ArrayList<Group>(sessionUser.getGroups());
}
return ownedGroups;
}
/**
* Creates a {@link User} object which acts a sub-admin (= group leader) for
* the given Group.
*
* @param group A persistent {@link Group} object to create a sub-admin for
* @return the persistent {@link User} object which acts a sub-admin
* @throws ShogunServiceException
*/
private User createSubadminForGroup(Group group) throws ShogunServiceException {
try {
// create a USER object as sub-admin
User subadmin = new User();
subadmin.setUser_country(group.getCountry());
subadmin.setUser_email(group.getMail());
subadmin.setUser_name("subadmin_" + group.getGroup_nr());
subadmin.setUser_street(group.getStreet());
subadmin.setUser_lang(group.getLanguage());
// TODO become more flexibel here, wrap to method
// set the default map conf
// TODO remove static ID !!!!!!!
MapConfig mapConfig =
(MapConfig)this.getDatabaseDao().getEntityById(1, MapConfig.class);
if (mapConfig != null) {
subadmin.setMapConfig(mapConfig);
}
// TODO become more flexibel here, wrap to method
// set the default wms proxy conf
WmsProxyConfig defaultWmsProxy =
(WmsProxyConfig)this.getDatabaseDao().getEntityById(1, WmsProxyConfig.class);
if (defaultWmsProxy != null) {
subadmin.setWmsProxyConfig(defaultWmsProxy);
}
// TODO become more flexibel here, wrap to method
// set the default wms proxy conf
WfsProxyConfig defaultWfsProxy =
(WfsProxyConfig)this.getDatabaseDao().getEntityById(1, WfsProxyConfig.class);
if (defaultWfsProxy != null) {
subadmin.setWfsProxyConfig(defaultWfsProxy);
}
subadmin.setApp_user(group.getApp_user());
subadmin.setCreated_at(group.getCreated_at());
subadmin.setUpdated_at(group.getUpdated_at());
// create a new random password and send it per mail to new user
String pw = Password.getRandomPassword(8);
PasswordEncoder pwencoder = new Md5PasswordEncoder();
String hashed = pwencoder.encodePassword(pw, null);
subadmin.setUser_password(hashed);
// save sub-admin to database
User persistentSubadmin =
this.getDatabaseDao().createUser(subadmin, false);
// send a mail with the new password
String mailtext = "Sehr geehrter Nutzer " + subadmin.getUser_name()
+ "\n\n";
mailtext += "Ihr Passwort zu SHOGun lautet \n\n";
mailtext += pw + "\n\n";
try {
Mail.send("localhost", 25, subadmin.getUser_email(), "admin",
"Registrierung bei SHOGun", mailtext);
} catch (Exception e) {
throw new ShogunServiceException(e.getMessage());
}
return persistentSubadmin;
} catch (Exception e) {
throw new ShogunServiceException(
"Error while creating sub-admin for group: " + e.getMessage(), e);
}
}
}