/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.api.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import org.openmrs.Person; import org.openmrs.Privilege; import org.openmrs.PrivilegeListener; import org.openmrs.Role; import org.openmrs.User; import org.openmrs.annotation.Authorized; import org.openmrs.annotation.Logging; import org.openmrs.api.APIAuthenticationException; import org.openmrs.api.APIException; import org.openmrs.api.CannotDeleteRoleWithChildrenException; import org.openmrs.api.UserService; import org.openmrs.api.context.Context; import org.openmrs.api.db.DAOException; import org.openmrs.api.db.LoginCredential; import org.openmrs.api.db.UserDAO; import org.openmrs.patient.impl.LuhnIdentifierValidator; import org.openmrs.util.OpenmrsUtil; import org.openmrs.util.PrivilegeConstants; import org.openmrs.util.RoleConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.transaction.annotation.Transactional; /** * Default implementation of the user service. This class should not be used on its own. The current * OpenMRS implementation should be fetched from the Context * * @see org.openmrs.api.UserService * @see org.openmrs.api.context.Context */ @Transactional public class UserServiceImpl extends BaseOpenmrsService implements UserService { protected final Logger log = LoggerFactory.getLogger(UserServiceImpl.class); protected UserDAO dao; @Autowired(required = false) List<PrivilegeListener> privilegeListeners; public UserServiceImpl() { } public void setUserDAO(UserDAO dao) { this.dao = dao; } /** * @see org.openmrs.api.UserService#createUser(org.openmrs.User, java.lang.String) */ @Override public User createUser(User user, String password) throws APIException { if (user.getUserId() != null) { throw new APIException("This method can be used for only creating new users"); } Context.requirePrivilege(PrivilegeConstants.ADD_USERS); checkPrivileges(user); // if a password wasn't supplied, throw an error if (password == null || password.length() < 1) { throw new APIException("User.creating.password.required", (Object[]) null); } if (hasDuplicateUsername(user)) { throw new DAOException("Username " + user.getUsername() + " or system id " + user.getSystemId() + " is already in use."); } // TODO Check required fields for user!! OpenmrsUtil.validatePassword(user.getUsername(), password, user.getSystemId()); return dao.saveUser(user, password); } /** * @see org.openmrs.api.UserService#getUser(java.lang.Integer) */ @Override @Transactional(readOnly = true) public User getUser(Integer userId) throws APIException { return dao.getUser(userId); } /** * @see org.openmrs.api.UserService#getUserByUsername(java.lang.String) */ @Override @Transactional(readOnly = true) public User getUserByUsername(String username) throws APIException { return dao.getUserByUsername(username); } /** * @see org.openmrs.api.UserService#hasDuplicateUsername(org.openmrs.User) */ @Override @Transactional(readOnly = true) public boolean hasDuplicateUsername(User user) throws APIException { return dao.hasDuplicateUsername(user.getUsername(), user.getSystemId(), user.getUserId()); } /** * @see org.openmrs.api.UserService#getUsersByRole(org.openmrs.Role) */ @Override @Transactional(readOnly = true) public List<User> getUsersByRole(Role role) throws APIException { List<Role> roles = new Vector<Role>(); roles.add(role); return Context.getUserService().getUsers(null, roles, false); } /** * @see org.openmrs.api.UserService#saveUser(org.openmrs.User) */ @Override @CacheEvict(value = "userSearchLocales", allEntries = true) public User saveUser(User user) throws APIException { if (user.getUserId() == null) { throw new APIException("This method can be called only to update existing users"); } Context.requirePrivilege(PrivilegeConstants.EDIT_USERS); checkPrivileges(user); if (hasDuplicateUsername(user)) { throw new DAOException("Username " + user.getUsername() + " or system id " + user.getSystemId() + " is already in use."); } return dao.saveUser(user, null); } /** * @see org.openmrs.api.UserService#voidUser(org.openmrs.User, java.lang.String) */ public User voidUser(User user, String reason) throws APIException { return Context.getUserService().retireUser(user, reason); } /** * @see org.openmrs.api.UserService#retireUser(org.openmrs.User, java.lang.String) */ @Override public User retireUser(User user, String reason) throws APIException { user.setRetired(true); user.setRetireReason(reason); user.setRetiredBy(Context.getAuthenticatedUser()); user.setDateRetired(new Date()); return saveUser(user); } /** * @see org.openmrs.api.UserService#unvoidUser(org.openmrs.User) */ public User unvoidUser(User user) throws APIException { return Context.getUserService().unretireUser(user); } /** * @see org.openmrs.api.UserService#unretireUser(org.openmrs.User) */ @Override public User unretireUser(User user) throws APIException { user.setRetired(false); user.setRetireReason(null); user.setRetiredBy(null); user.setDateRetired(null); return saveUser(user); } /** * @see org.openmrs.api.UserService#getAllUsers() */ @Override @Transactional(readOnly = true) public List<User> getAllUsers() throws APIException { return dao.getAllUsers(); } /** * @see org.openmrs.api.UserService#getAllPrivileges() */ @Override @Transactional(readOnly = true) public List<Privilege> getAllPrivileges() throws APIException { return dao.getAllPrivileges(); } /** * @see org.openmrs.api.UserService#getPrivilege(java.lang.String) */ @Override @Transactional(readOnly = true) public Privilege getPrivilege(String p) throws APIException { return dao.getPrivilege(p); } /** * @see org.openmrs.api.UserService#purgePrivilege(org.openmrs.Privilege) */ @Override public void purgePrivilege(Privilege privilege) throws APIException { if (OpenmrsUtil.getCorePrivileges().keySet().contains(privilege.getPrivilege())) { throw new APIException("Privilege.cannot.delete.core", (Object[]) null); } dao.deletePrivilege(privilege); } /** * @see org.openmrs.api.UserService#savePrivilege(org.openmrs.Privilege) */ @Override public Privilege savePrivilege(Privilege privilege) throws APIException { return dao.savePrivilege(privilege); } /** * @see org.openmrs.api.UserService#getAllRoles() */ @Override @Transactional(readOnly = true) public List<Role> getAllRoles() throws APIException { return dao.getAllRoles(); } /** * @see org.openmrs.api.UserService#getRole(java.lang.String) */ @Override @Transactional(readOnly = true) public Role getRole(String r) throws APIException { return dao.getRole(r); } /** * @see org.openmrs.api.UserService#purgeRole(org.openmrs.Role) */ @Override public void purgeRole(Role role) throws APIException { if (role == null || role.getRole() == null) { return; } if (OpenmrsUtil.getCoreRoles().keySet().contains(role.getRole())) { throw new APIException("Role.cannot.delete.core", (Object[]) null); } if (role.hasChildRoles()) { throw new CannotDeleteRoleWithChildrenException(); } dao.deleteRole(role); } /** * @see org.openmrs.api.UserService#saveRole(org.openmrs.Role) */ @Override public Role saveRole(Role role) throws APIException { // make sure one of the parents of this role isn't itself...this would // cause an infinite loop if (role.getAllParentRoles().contains(role)) { throw new APIException("Role.cannot.inherit.descendant", (Object[]) null); } checkPrivileges(role); return dao.saveRole(role); } /** * @see org.openmrs.api.UserService#changePassword(java.lang.String, java.lang.String) */ @Override public void changePassword(String pw, String pw2) throws APIException { User user = Context.getAuthenticatedUser(); changePassword(user, pw, pw2); } /** * @see org.openmrs.api.UserService#changeHashedPassword(User, String, String) */ @Override public void changeHashedPassword(User user, String hashedPassword, String salt) throws APIException { dao.changeHashedPassword(user, hashedPassword, salt); } /** * @see org.openmrs.api.UserService#changeQuestionAnswer(User, String, String) */ @Override public void changeQuestionAnswer(User u, String question, String answer) throws APIException { dao.changeQuestionAnswer(u, question, answer); } /** * @see org.openmrs.api.UserService#changeQuestionAnswer(java.lang.String, java.lang.String, * java.lang.String) */ @Override public void changeQuestionAnswer(String pw, String q, String a) { dao.changeQuestionAnswer(pw, q, a); } /** * @see org.openmrs.api.UserService#isSecretAnswer(org.openmrs.User, java.lang.String) */ @Override @Transactional(readOnly = true) public boolean isSecretAnswer(User u, String answer) { return dao.isSecretAnswer(u, answer); } /** * @see org.openmrs.api.UserService#getUsersByName(java.lang.String, java.lang.String, boolean) */ @Override @Transactional(readOnly = true) public List<User> getUsersByName(String givenName, String familyName, boolean includeVoided) throws APIException { return dao.getUsersByName(givenName, familyName, includeVoided); } /** * @see org.openmrs.api.UserService#getUsersByPerson(org.openmrs.Person, boolean) */ @Override @Transactional(readOnly = true) public List<User> getUsersByPerson(Person person, boolean includeRetired) throws APIException { return dao.getUsersByPerson(person, includeRetired); } /** * @see org.openmrs.api.UserService#getUsers(java.lang.String, java.util.List, boolean) */ @Override @Transactional(readOnly = true) public List<User> getUsers(String nameSearch, List<Role> roles, boolean includeVoided) throws APIException { return Context.getUserService().getUsers(nameSearch, roles, includeVoided, null, null); } /** * Convenience method to check if the authenticated user has all privileges they are giving out * * @param new user that has privileges */ private void checkPrivileges(User user) { Collection<Role> roles = user.getAllRoles(); List<String> requiredPrivs = new Vector<String>(); for (Role r : roles) { if (r.getRole().equals(RoleConstants.SUPERUSER) && !Context.hasPrivilege(PrivilegeConstants.ASSIGN_SYSTEM_DEVELOPER_ROLE)) { throw new APIException("User.you.must.have.role", new Object[] { RoleConstants.SUPERUSER }); } if (r.getPrivileges() != null) { for (Privilege p : r.getPrivileges()) { if (!Context.hasPrivilege(p.getPrivilege())) { requiredPrivs.add(p.getPrivilege()); } } } } if (requiredPrivs.size() == 1) { throw new APIException("User.you.must.have.privilege", new Object[] { requiredPrivs.get(0) }); } else if (requiredPrivs.size() > 1) { StringBuilder txt = new StringBuilder("You must have the following privileges in order to assign them: "); for (String s : requiredPrivs) { txt.append(s).append(", "); } throw new APIException(txt.substring(0, txt.length() - 2)); } } /** * @see org.openmrs.api.UserService#setUserProperty(User, String, String) */ @Override public User setUserProperty(User user, String key, String value) { if (user != null) { if (!Context.hasPrivilege(PrivilegeConstants.EDIT_USERS) && !user.equals(Context.getAuthenticatedUser())) { throw new APIException("you.are.not.authorized.change.properties", new Object[] { user.getUserId() }); } user.setUserProperty(key, value); try { Context.addProxyPrivilege(PrivilegeConstants.EDIT_USERS); Context.getUserService().saveUser(user); } finally { Context.removeProxyPrivilege(PrivilegeConstants.EDIT_USERS); } } return user; } /** * @see org.openmrs.api.UserService#removeUserProperty(org.openmrs.User, java.lang.String) */ @Override public User removeUserProperty(User user, String key) { if (user != null) { // if the current user isn't allowed to edit users and // the user being edited is not the current user, throw an // exception if (!Context.hasPrivilege(PrivilegeConstants.EDIT_USERS) && !user.equals(Context.getAuthenticatedUser())) { throw new APIException("you.are.not.authorized.change.properties", new Object[] { user.getUserId() }); } user.removeUserProperty(key); try { Context.addProxyPrivilege(PrivilegeConstants.EDIT_USERS); Context.getUserService().saveUser(user); } finally { Context.removeProxyPrivilege(PrivilegeConstants.EDIT_USERS); } } return user; } /** * Generates system ids based on the following algorithm scheme: user_id-check digit * * @see org.openmrs.api.UserService#generateSystemId() */ @Override @Transactional(readOnly = true) public String generateSystemId() { // Hardcoding Luhn algorithm since all existing openmrs user ids have // had check digits generated this way. LuhnIdentifierValidator liv = new LuhnIdentifierValidator(); String systemId; Integer offset = 0; do { // generate and increment the system id if necessary Integer generatedId = dao.generateSystemId() + offset++; systemId = generatedId.toString(); try { systemId = liv.getValidIdentifier(systemId); } catch (Exception e) { log.error("error getting check digit", e); return systemId; } // loop until we find a system id that no one has } while (dao.hasDuplicateUsername(null, systemId, null)); return systemId; } /** * @see org.openmrs.api.UserService#purgeUser(org.openmrs.User) */ @Override public void purgeUser(User user) throws APIException { dao.deleteUser(user); } /** * @see org.openmrs.api.UserService#purgeUser(org.openmrs.User, boolean) */ @Override public void purgeUser(User user, boolean cascade) throws APIException { if (cascade) { throw new APIException("cascade.do.not.think", (Object[]) null); } dao.deleteUser(user); } /** * Convenience method to check if the authenticated user has all privileges they are giving out * to the new role * * @param new user that has privileges */ private void checkPrivileges(Role role) { Collection<Privilege> privileges = role.getPrivileges(); if (privileges != null) { for (Privilege p : privileges) { if (!Context.hasPrivilege(p.getPrivilege())) { throw new APIAuthenticationException("Privilege required: " + p); } } } } /** * @see org.openmrs.api.UserService#getPrivilegeByUuid(java.lang.String) */ @Override @Transactional(readOnly = true) public Privilege getPrivilegeByUuid(String uuid) throws APIException { return dao.getPrivilegeByUuid(uuid); } /** * @see org.openmrs.api.UserService#getRoleByUuid(java.lang.String) */ @Override @Transactional(readOnly = true) public Role getRoleByUuid(String uuid) throws APIException { return dao.getRoleByUuid(uuid); } /** * @see org.openmrs.api.UserService#getUserByUuid(java.lang.String) */ @Override @Transactional(readOnly = true) public User getUserByUuid(String uuid) throws APIException { return dao.getUserByUuid(uuid); } /** * @see UserService#getCountOfUsers(String, List, boolean) */ @Override @Transactional(readOnly = true) public Integer getCountOfUsers(String name, List<Role> roles, boolean includeRetired) { if (name != null) { name = name.replace(", ", " "); } // if the authenticated role is in the list of searched roles, then all // persons should be searched Role auth_role = getRole(RoleConstants.AUTHENTICATED); if (roles.contains(auth_role)) { return dao.getCountOfUsers(name, new Vector<Role>(), includeRetired); } return dao.getCountOfUsers(name, roles, includeRetired); } /** * @see UserService#getUsers(String, List, boolean, Integer, Integer) */ @Override @Transactional(readOnly = true) public List<User> getUsers(String name, List<Role> roles, boolean includeRetired, Integer start, Integer length) throws APIException { if (name != null) { name = name.replace(", ", " "); } if (roles == null) { roles = new Vector<Role>(); } // add the requested roles and all child roles for consideration Set<Role> allRoles = new HashSet<Role>(); for (Role r : roles) { allRoles.add(r); allRoles.addAll(r.getAllChildRoles()); } // if the authenticated role is in the list of searched roles, then all // persons should be searched Role auth_role = getRole(RoleConstants.AUTHENTICATED); if (roles.contains(auth_role)) { return dao.getUsers(name, new Vector<Role>(), includeRetired, start, length); } return dao.getUsers(name, new ArrayList<Role>(allRoles), includeRetired, start, length); } /** * @see UserService#notifyPrivilegeListeners(User, String, boolean) */ @Override @Transactional(readOnly = true) public void notifyPrivilegeListeners(User user, String privilege, boolean hasPrivilege) { if (privilegeListeners != null) { for (PrivilegeListener privilegeListener : privilegeListeners) { try { privilegeListener.privilegeChecked(user, privilege, hasPrivilege); } catch (Exception e) { log.error("Privilege listener has failed", e); } } } } @Override public User saveUserProperty(String key, String value) { User user = Context.getAuthenticatedUser(); if (user == null) { throw new APIException("no.authenticated.user.found", (Object[]) null); } user.setUserProperty(key, value); return dao.saveUser(user, null); } @Override public User saveUserProperties(Map<String, String> properties) { User user = Context.getAuthenticatedUser(); if (user == null) { throw new APIException("no.authenticated.user.found", (Object[]) null); } user.getUserProperties().clear(); for (Map.Entry<String, String> entry : properties.entrySet()) { user.setUserProperty(entry.getKey(), entry.getValue()); } return dao.saveUser(user, null); } /** * @see UserService#changePassword(User, String, String) */ @Override @Authorized(PrivilegeConstants.EDIT_USER_PASSWORDS) @Logging(ignoredArgumentIndexes = { 1, 2 }) public void changePassword(User user, String oldPassword, String newPassword) throws APIException { if (user.getUserId() == null) { throw new APIException("user.must.exist", (Object[]) null); } if (oldPassword == null) { if (!Context.hasPrivilege(PrivilegeConstants.EDIT_USER_PASSWORDS)) { throw new APIException("null.old.password.privilege.required", (Object[]) null); } } else if (!dao.getLoginCredential(user).checkPassword(oldPassword)) { throw new APIException("old.password.not.correct", (Object[]) null); } updatePassword(user, newPassword); } private void updatePassword(User user, String newPassword) { OpenmrsUtil.validatePassword(user.getUsername(), newPassword, user.getSystemId()); dao.changePassword(user, newPassword); } @Override public void changePassword(User user, String newPassword) throws APIException { updatePassword(user, newPassword); } @Override public void changePasswordUsingSecretAnswer(String secretAnswer, String pw) throws APIException { User user = Context.getAuthenticatedUser(); if(!isSecretAnswer(user, secretAnswer)) { throw new APIException("secret.answer.not.correct", (Object[]) null); } updatePassword(user, pw); } @Override public String getSecretQuestion(User user) throws APIException { if (user.getUserId() != null) { LoginCredential loginCredential = dao.getLoginCredential(user); return loginCredential.getSecretQuestion(); } else { return null; } } }