/**
* Abiquo community edition
* cloud management application for hybrid clouds
* Copyright (C) 2008-2010 - Abiquo Holdings S.L.
*
* This application is free software; you can redistribute it and/or
* modify it under the terms of the GNU LESSER GENERAL PUBLIC
* LICENSE as published by the Free Software Foundation under
* version 3 of the License
*
* This software 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
* LESSER GENERAL PUBLIC LICENSE v.3 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package com.abiquo.api.services;
import static com.abiquo.api.util.URIResolver.buildPath;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.EntityManager;
import javax.ws.rs.core.MultivaluedMap;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Hibernate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.abiquo.api.config.ConfigService;
import com.abiquo.api.exceptions.APIError;
import com.abiquo.api.exceptions.ConflictException;
import com.abiquo.api.resources.EnterpriseResource;
import com.abiquo.api.resources.EnterprisesResource;
import com.abiquo.api.resources.RoleResource;
import com.abiquo.api.resources.RolesResource;
import com.abiquo.api.spring.security.AbiquoUserDetails;
import com.abiquo.api.spring.security.SecurityService;
import com.abiquo.api.util.URIResolver;
import com.abiquo.model.enumerator.Privileges;
import com.abiquo.model.rest.RESTLink;
import com.abiquo.server.core.enterprise.Enterprise;
import com.abiquo.server.core.enterprise.EnterpriseRep;
import com.abiquo.server.core.enterprise.Privilege;
import com.abiquo.server.core.enterprise.Role;
import com.abiquo.server.core.enterprise.User;
import com.abiquo.server.core.enterprise.UserDto;
import com.abiquo.server.core.enterprise.User.AuthType;
import com.abiquo.tracer.ComponentType;
import com.abiquo.tracer.EventType;
import com.abiquo.tracer.SeverityType;
@Service
@Transactional(readOnly = true)
public class UserService extends DefaultApiService
{
private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);
@Autowired
EnterpriseRep repo;
@Autowired
SecurityService securityService;
public UserService()
{
}
// use this to initialize it for tests
public UserService(final EntityManager em)
{
repo = new EnterpriseRep(em);
securityService = new SecurityService();
}
/**
* Based on the spring authentication context.
*
* @see SecurityContextHolder
*/
public User getCurrentUser()
{
if (SecurityContextHolder.getContext().getAuthentication().getPrincipal() instanceof AbiquoUserDetails)
{
AbiquoUserDetails details =
(AbiquoUserDetails) SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
AuthType authType =
AuthType.valueOf(details.getAuthType() != null ? details.getAuthType()
: AuthType.ABIQUO.name());
return repo.getUserByAuth(details.getUsername(), authType);
}
else
{ // Backward compatibility and bzngine
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
return repo.getUserByAuth(userName, AuthType.ABIQUO);
}
}
public Collection<User> getUsersByEnterprise(final String enterpriseId, final String filter,
final String order, final boolean desc)
{
return getUsersByEnterprise(enterpriseId, filter, order, desc, false, 0, 25);
}
public Collection<User> getUsersByEnterprise(final String enterpriseId, final String filter,
String order, final boolean desc, final boolean connected, final Integer page,
final Integer numResults)
{
Enterprise enterprise = null;
User user = getCurrentUser();
if (!enterpriseId.equals("_"))
{
enterprise = findEnterprise(Integer.valueOf(enterpriseId));
// [ABICLOUDPREMIUM-1310] Cloud admin can view all. Enterprise admin and users can only
// view their enterprise: check that the provided id corresponds to their enterprise,
// and fail if the id is invalid
checkCurrentEnterpriseForUsers(enterprise);
}
else
{
// [ABICLOUDPREMIUM-1310] Cloud admin can view all. Enterprise admin and users can only
// view their enterprise, so force it if necessary. Here we won't fail, because no id
// was provided in the request
// if (user.getRole().getType() != Role.Type.SYS_ADMIN)
if (!securityService.isCloudAdmin())
{
enterprise = user.getEnterprise();
}
}
// [ABICLOUDPREMIUM-1310] If all the checks are valid, we still need to restrict to the
// current user if the role of the requestes is a standard user
// if (user.getRole().getType() == Role.Type.USER)
// [ROLES & PRIVILEGES] User response depends on current user's privileges)
if (!securityService.hasPrivilege(Privileges.USERS_VIEW))
{
return Collections.singletonList(user);
}
if (StringUtils.isEmpty(order))
{
order = User.NAME_PROPERTY;
}
Collection<User> users =
repo
.findUsersByEnterprise(enterprise, filter, order, desc, connected, page, numResults);
// Refresh all entities to avioid lazys
for (User u : users)
{
Hibernate.initialize(u.getEnterprise());
Hibernate.initialize(u.getRole());
for (Privilege p : u.getRole().getPrivileges())
{
Hibernate.initialize(p);
}
}
return users;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public User addUser(final UserDto dto, final Integer enterpriseId)
{
Role role = findRole(dto);
return addUser(dto, enterpriseId, role);
}
public User addUser(final UserDto dto, final Integer enterpriseId, final Role role)
{
Enterprise enterprise = findEnterprise(enterpriseId);
checkEnterpriseAdminCredentials(enterprise);
if (dto.getPassword() == null || dto.getPassword().isEmpty())
{
addValidationErrors(APIError.USER_PASSWORD_IS_NECESSARY);
flushErrors();
}
if (dto.getNick() == null || dto.getNick().isEmpty())
{
addValidationErrors(APIError.USER_NICK_IS_NECESSARY);
flushErrors();
}
if (dto.getName() == null || dto.getName().isEmpty())
{
addValidationErrors(APIError.USER_NAME_IS_NECESSARY);
flushErrors();
}
User user =
enterprise.createUser(role, dto.getName(), dto.getSurname(), dto.getEmail(), dto
.getNick(), encrypt(dto.getPassword()), dto.getLocale());
user.setActive(dto.isActive() ? 1 : 0);
user.setDescription(dto.getDescription());
validate(user);
if (!securityService.hasPrivilege(Privileges.USERS_PROHIBIT_VDC_RESTRICTION, user)
&& !StringUtils.isBlank(dto.getAvailableVirtualDatacenters()))
{
user.setAvailableVirtualDatacenters(dto.getAvailableVirtualDatacenters());
}
if (!user.isValid())
{
addValidationErrors(user.getValidationErrors());
flushErrors();
}
if (repo.existAnyUserWithNickAndAuth(user.getNick(), AuthType.ABIQUO))
{
addConflictErrors(APIError.USER_DUPLICATED_NICK);
flushErrors();
}
if (!emailIsValid(user.getEmail()))
{
addValidationErrors(APIError.EMAIL_IS_INVALID);
flushErrors();
}
repo.insertUser(user);
tracer
.log(SeverityType.INFO, ComponentType.USER, EventType.USER_CREATE, "user.created", user
.getName(), enterprise.getName(), user.getName(), user.getSurname(), user.getRole());
return user;
}
public User getUser(final Integer id)
{
return getUser(id, false);
}
public User getUser(final Integer id, final Boolean skipCredentials)
{
User user = repo.findUserById(id);
if (user == null)
{
addNotFoundErrors(APIError.USER_NON_EXISTENT);
flushErrors();
}
if (!skipCredentials)
{
checkUserCredentialsForSelfUser(user, user.getEnterprise());
}
return user;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public User modifyUser(final Integer userId, final UserDto user)
{
if (!securityService.hasPrivilege(Privileges.USERS_MANAGE_USERS)
&& !securityService.hasPrivilege(Privileges.USERS_MANAGE_OTHER_ENTERPRISES))
{
if (!getCurrentUser().getId().equals(userId))
{
securityService.requirePrivilege(Privileges.USERS_MANAGE_USERS);
}
}
User old = repo.findUserById(userId);
if (old == null)
{
addNotFoundErrors(APIError.USER_NON_EXISTENT);
flushErrors();
}
checkUserCredentialsForSelfUser(old, old.getEnterprise());
if (securityService.canManageOtherEnterprises(old)
&& !securityService.canManageOtherEnterprises())
{
addConflictErrors(APIError.NOT_ENOUGH_PRIVILEGES);
flushErrors();
}
old.setActive(user.isActive() ? 1 : 0);
old.setEmail(user.getEmail());
old.setLocale(user.getLocale());
old.setName(user.getName());
if (!StringUtils.isEmpty(user.getPassword()))
{
// Password must only be updated if it is provided
old.setPassword(encrypt(user.getPassword()));
}
old.setSurname(user.getSurname());
if (!old.getNick().equalsIgnoreCase(user.getNick()))
{
addConflictErrors(APIError.USER_NICK_CANNOT_BE_CHANGED);
flushErrors();
}
old.setDescription(user.getDescription());
if (!securityService.hasPrivilege(Privileges.USERS_PROHIBIT_VDC_RESTRICTION, old))
{
if (StringUtils.isBlank(user.getAvailableVirtualDatacenters()))
{
user.setAvailableVirtualDatacenters(null);
}
old.setAvailableVirtualDatacenters(user.getAvailableVirtualDatacenters());
}
if (!emailIsValid(user.getEmail()))
{
addValidationErrors(APIError.EMAIL_IS_INVALID);
flushErrors();
}
String authMode = ConfigService.getSecurityMode();
if (user.searchLink(RoleResource.ROLE) != null)
{
Role newRole = findRole(user);
if (authMode.equalsIgnoreCase(User.AuthType.LDAP.toString()))
{
if (!old.getRole().getId().equals(newRole.getId()))
{
// In ldap mode it is not possible to edit user's role
throw new ConflictException(APIError.NOT_EDIT_USER_ROLE_LDAP_MODE);
}
}
old.setRole(newRole);
}
Enterprise newEnt = null;
if (user.searchLink(EnterpriseResource.ENTERPRISE) != null)
{
newEnt = findEnterprise(getEnterpriseID(user));
}
if (securityService.hasPrivilege(Privileges.USERS_MANAGE_OTHER_ENTERPRISES))
{
if (user.searchLink(EnterpriseResource.ENTERPRISE) != null)
{
old.setEnterprise(newEnt);
}
}
else if (securityService.hasPrivilege(Privileges.ENTERPRISE_ADMINISTER_ALL))
{
if (getCurrentUser().getId().equals(user.getId()))
{
if (user.searchLink(EnterpriseResource.ENTERPRISE) != null)
{
old.setEnterprise(newEnt);
}
}
}
if (!old.isValid())
{
addValidationErrors(old.getValidationErrors());
flushErrors();
}
if (repo.existAnyOtherUserWithNick(old, old.getNick()))
{
addConflictErrors(APIError.USER_DUPLICATED_NICK);
flushErrors();
}
updateUser(old);
tracer.log(SeverityType.INFO, ComponentType.USER, EventType.USER_MODIFY, "user.modified",
old.getName(), old.getEnterprise().getName(), old.getName(), old.getSurname(), old
.getRole());
return old;
}
public User updateUser(final User user)
{
repo.updateUser(user);
return user;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void removeUser(final Integer id)
{
User user = getUser(id);
// user can not delete himself
User logged = getCurrentUser();
if (logged.getId() == user.getId())
{
addConflictErrors(APIError.USER_DELETING_HIMSELF);
flushErrors();
}
checkEnterpriseAdminCredentials(user.getEnterprise());
// Cloud Admins should only be editable by other Cloud Admins
// if (user.getRole().getType() == Role.Type.SYS_ADMIN
// && getCurrentUser().getRole().getType() != Role.Type.SYS_ADMIN)
if (securityService.canManageOtherEnterprises(user)
&& !securityService.canManageOtherEnterprises())
{
addForbiddenErrors(APIError.NOT_ENOUGH_PRIVILEGES);
flushErrors();
}
repo.removeUser(user);
tracer.log(SeverityType.INFO, ComponentType.USER, EventType.USER_DELETE, "user.deleted",
user.getName(), user.getEnterprise().getName(), user.getName(), user.getSurname(), user
.getRole());
}
public boolean isAssignedTo(final Integer enterpriseId, final Integer userId)
{
User user = getUser(userId);
return user != null && user.getEnterprise().getId().equals(enterpriseId);
}
private Enterprise findEnterprise(final Integer enterpriseId)
{
Enterprise enterprise = repo.findById(enterpriseId);
if (enterprise == null)
{
addNotFoundErrors(APIError.NON_EXISTENT_ENTERPRISE);
flushErrors();
}
return enterprise;
}
public User findUserByEnterprise(final Integer userId, final Enterprise enterprise)
{
User user = repo.findUserByEnterprise(userId, enterprise);
if (user == null)
{
addNotFoundErrors(APIError.USER_NON_EXISTENT);
flushErrors();
}
return user;
}
private Role findRole(final UserDto dto)
{
Role role = repo.findRoleById(getRoleId(dto));
if (role == null)
{
addNotFoundErrors(APIError.NON_EXISTENT_ROLE);
flushErrors();
}
return role;
}
private Integer getRoleId(final UserDto user)
{
RESTLink role = user.searchLink(RoleResource.ROLE);
if (role == null)
{
addValidationErrors(APIError.MISSING_ROLE_LINK);
flushErrors();
}
String buildPath = buildPath(RolesResource.ROLES_PATH, RoleResource.ROLE_PARAM);
MultivaluedMap<String, String> roleValues =
URIResolver.resolveFromURI(buildPath, role.getHref());
if (roleValues == null || !roleValues.containsKey(RoleResource.ROLE))
{
addNotFoundErrors(APIError.ROLE_PARAM_NOT_FOUND);
flushErrors();
}
Integer roleId = Integer.valueOf(roleValues.getFirst(RoleResource.ROLE));
return roleId;
}
private Integer getEnterpriseID(final UserDto user)
{
RESTLink ent = user.searchLink(EnterpriseResource.ENTERPRISE);
String buildPath =
buildPath(EnterprisesResource.ENTERPRISES_PATH, EnterpriseResource.ENTERPRISE_PARAM);
MultivaluedMap<String, String> values =
URIResolver.resolveFromURI(buildPath, ent.getHref());
Integer entId = Integer.valueOf(values.getFirst(EnterpriseResource.ENTERPRISE));
return entId;
}
public void checkEnterpriseAdminCredentials(final Enterprise enterprise)
{
User user = getCurrentUser();
boolean sameEnterprise = enterprise.getId().equals(user.getEnterprise().getId());
// Role.Type role = user.getRole().getType();
//
// if ((role == Role.Type.ENTERPRISE_ADMIN && !enterprise.equals(user.getEnterprise()))
// || role == Role.Type.USER)
if (securityService.isEnterpriseAdmin() && !sameEnterprise
|| securityService.isStandardUser())
{
throw new AccessDeniedException("");
}
}
public String enterpriseWithBlockedRoles(final Enterprise enterprise)
{
Collection<User> users = repo.findUsersByEnterprise(enterprise);
for (User user : users)
{
if (user.getRole().isBlocked())
{
return user.getRole().getName().toString();
}
}
return "";
}
private void checkUserCredentialsForSelfUser(final User selfUser, final Enterprise enterprise)
{
User user = getCurrentUser();
boolean sameEnterprise = enterprise.getId().equals(user.getEnterprise().getId());
boolean sameUser = user.getId().equals(selfUser.getId());
// Role.Type role = user.getRole().getType();
//
// if ((role == Role.Type.ENTERPRISE_ADMIN && !enterprise.equals(user.getEnterprise()))
// || (role == Role.Type.USER && user.getId() != selfUser.getId()))
if (securityService.isEnterpriseAdmin() && !sameEnterprise
|| securityService.isStandardUser() && !sameUser)
{
throw new AccessDeniedException("");
}
}
public void checkCurrentEnterprise(final Enterprise enterprise)
{
User user = getCurrentUser();
boolean sameEnterprise = enterprise.getId().equals(user.getEnterprise().getId());
// Role.Type role = user.getRole().getType();
// if ((role == Role.Type.ENTERPRISE_ADMIN || role == Role.Type.USER) && !sameEnterprise)
if (!sameEnterprise
&& !securityService.hasPrivilege(Privileges.USERS_MANAGE_OTHER_ENTERPRISES)
&& !securityService.hasPrivilege(Privileges.USERS_MANAGE_ROLES_OTHER_ENTERPRISES)
&& !securityService.hasPrivilege(Privileges.ENTERPRISE_ENUMERATE)
&& !securityService.hasPrivilege(Privileges.ENTERPRISE_ADMINISTER_ALL)
&& !securityService.hasPrivilege(Privileges.PHYS_DC_ENUMERATE))
{
throw new AccessDeniedException("Missing privilege to get info from other enterprises");
}
}
public void checkCurrentEnterpriseForUsers(final Enterprise enterprise)
{
User user = getCurrentUser();
boolean sameEnterprise = enterprise.getId().equals(user.getEnterprise().getId());
if (!sameEnterprise
&& !securityService.hasPrivilege(Privileges.USERS_MANAGE_OTHER_ENTERPRISES))
{
throw new AccessDeniedException("Missing privilege to get info from other enterprises");
}
}
public void checkCurrentEnterpriseForPostMethods(final Enterprise enterprise)
{
User user = getCurrentUser();
boolean sameEnterprise = enterprise.getId().equals(user.getEnterprise().getId());
if (!sameEnterprise && !securityService.hasPrivilege(Privileges.ENTERPRISE_ADMINISTER_ALL))
{
throw new AccessDeniedException("Missing privilege to manage info from other enterprises");
}
}
/**
* Retrieves the user by nick in the DB. This method assumes that the login is unique.
*/
public User getUserByLogin(final String login)
{
return repo.getUserByUserName(login);
}
private Boolean emailIsValid(final String email)
{
if (email != null && !email.isEmpty())
{
final Pattern pattern;
final Matcher matchers;
final String EMAIL_PATTERN =
"[a-z0-9A-Z!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9A-Z!#$%&'*+/=?^_`{|}~-]+)*@"
+ "(?:[a-z0-9A-Z](?:[a-z0-9A-Z-]*[a-z0-9A-Z])?\\.)+[a-z0-9A-Z](?:[a-z0-9A-Z-]*[a-z0-9A-Z])?";
pattern = Pattern.compile(EMAIL_PATTERN);
matchers = pattern.matcher(email);
return matchers.matches();
}
else
{
return true;
}
}
private String encrypt(final String toEncrypt)
{
MessageDigest messageDigest = null;
try
{
messageDigest = MessageDigest.getInstance("MD5");
}
catch (NoSuchAlgorithmException e)
{
LOGGER.debug("cannot get the instance of messageDigest", e);
// revise if the method is called from other method
addUnexpectedErrors(APIError.STATUS_BAD_REQUEST);
flushErrors();
}
messageDigest.reset();
messageDigest.update(toEncrypt.getBytes(Charset.forName("UTF8")));
final byte[] resultByte = messageDigest.digest();
return new String(Hex.encodeHex(resultByte));
}
public SecurityService getSecurityService()
{
return securityService;
}
/**
* Check if a user has permissions to use the given virtual datacenter
*
* @param username nick of the given User
* @param authtype authentication type of the given User
* @param privileges array of strings with all privileges names from the given User role
* @param idVdc identifier from virtual datacenter to check
* @return True if user is allowed to user the given virtual datacenter
*/
public boolean isUserAllowedToUseVirtualDatacenter(final String username,
final String authtype, final String[] privileges, final Integer idVdc)
{
return repo.isUserAllowedToUseVirtualDatacenter(username, authtype, privileges, idVdc);
}
/**
* Check if a user has permissions to use or see the given enterprise
*
* @param username nick of the given User
* @param authtype authentication type of the given User
* @param privileges array of strings with all privileges names from the given User role
* @param idEnteprise identifier from enterprise to check
* @return True if user is allowed to use or see the given enterprise
*/
public boolean isUserAllowedToEnterprise(final String username, final String authtype,
final String[] privileges, final Integer idEnterprise)
{
return repo.isUserAllowedToEnterprise(username, authtype, privileges, idEnterprise);
}
}