Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.haulmont.cuba.security.app; import com.haulmont.bali.util.Preconditions; import com.haulmont.chile.core.model.MetaClass; import com.haulmont.cuba.core.*; import com.haulmont.cuba.core.app.EmailerAPI; import com.haulmont.cuba.core.app.ServerConfig; import com.haulmont.cuba.core.global.*; import com.haulmont.cuba.security.entity.*; import groovy.text.SimpleTemplateEngine; import groovy.text.Template; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import javax.annotation.Nullable; import javax.inject.Inject; import java.io.IOException; import java.io.StringWriter; import java.util.*; import static com.haulmont.bali.util.Preconditions.checkNotNullArgument; @Service(UserManagementService.NAME) public class UserManagementServiceBean implements UserManagementService { private static final Logger log = LoggerFactory.getLogger(UserManagementServiceBean.class); protected static final String GROUP_COPY_VIEW = "group.copy"; protected static final String ROLE_COPY_VIEW = "role.copy"; protected static final String MOVE_USER_TO_GROUP_VIEW = "user.moveToGroup"; protected static final String RESET_PASSWORD_VIEW = "user.resetPassword"; protected static final String CHANGE_PASSWORD_VIEW = "user.changePassword"; protected static final String CHECK_PASSWORD_VIEW = "user.check"; @Inject protected Persistence persistence; @Inject protected Metadata metadata; @Inject protected PasswordEncryption passwordEncryption; @Inject protected EmailerAPI emailerAPI; @Inject protected Resources resources; @Inject protected Scripting scripting; @Inject protected ServerConfig serverConfig; @Inject protected UserSessionSource userSessionSource; @Inject protected MessageTools messageTools; @Inject protected Security security; @Inject protected Messages messages; protected void checkUpdatePermission(Class entityClass) { checkPermission(entityClass, EntityOp.UPDATE); } protected void checkPermission(Class entityClass, EntityOp op) { MetaClass metaClass = metadata.getClassNN(entityClass); if (!security.isEntityOpPermitted(metaClass, op)) { throw new AccessDeniedException(PermissionType.ENTITY_OP, metaClass.getName()); } } @Override public Group copyAccessGroup(UUID accessGroupId) { checkNotNullArgument(accessGroupId, "Null access group id"); checkUpdatePermission(Group.class); Group clone = null; Transaction tx = persistence.getTransaction(); try { EntityManager em = persistence.getEntityManager(); Query groupNamesQuery = em.createQuery("select g.name from sec$Group g"); @SuppressWarnings("unchecked") Set<String> groupNames = new HashSet<>(groupNamesQuery.getResultList()); Group accessGroup = em.find(Group.class, accessGroupId, GROUP_COPY_VIEW); if (accessGroup == null) throw new IllegalStateException("Unable to find specified access group with id: " + accessGroupId); clone = cloneGroup(accessGroup, accessGroup.getParent(), groupNames, em); tx.commit(); } finally { tx.end(); } return clone; } @Override public Role copyRole(UUID roleId) { checkNotNullArgument(roleId, "Null access role id"); checkUpdatePermission(Role.class); Role clone = null; Transaction tx = persistence.getTransaction(); try { EntityManager em = persistence.getEntityManager(); Query roleNamesQuery = em.createQuery("select g.name from sec$Role g"); @SuppressWarnings("unchecked") Set<String> roleNames = new HashSet<>(roleNamesQuery.getResultList()); Role role = em.find(Role.class, roleId, ROLE_COPY_VIEW); if (role == null) throw new IllegalStateException("Unable to find specified role with id: " + roleId); clone = cloneRole(role, roleNames, em); clone.setDefaultRole(false); tx.commit(); } finally { tx.end(); } return clone; } @Override public Integer moveUsersToGroup(List<UUID> userIds, @Nullable UUID targetAccessGroupId) { checkNotNullArgument(userIds, "Null users list"); checkUpdatePermission(User.class); if (userIds.isEmpty()) return 0; Transaction tx = persistence.getTransaction(); int modifiedUsers = 0; try { EntityManager em = persistence.getEntityManager(); Group targetAccessGroup = null; if (targetAccessGroupId != null) { targetAccessGroup = em.find(Group.class, targetAccessGroupId); if (targetAccessGroup == null) throw new IllegalStateException( "Could not found target access group with id: " + targetAccessGroupId); } TypedQuery<User> query = em.createQuery("select u from sec$User u where u.id in :userIds", User.class); query.setParameter("userIds", userIds); query.setViewName(MOVE_USER_TO_GROUP_VIEW); List<User> users = query.getResultList(); if (users == null || users.size() != userIds.size()) throw new IllegalStateException("Not all users found in database"); for (User user : users) { if (!Objects.equals(user.getGroup(), targetAccessGroup)) { user.setGroup(targetAccessGroup); modifiedUsers++; } } tx.commit(); } finally { tx.end(); } return modifiedUsers; } @Override public Integer changePasswordsAtLogonAndSendEmails(List<UUID> userIds) { checkNotNullArgument(userIds, "Null users list"); checkUpdatePermission(User.class); if (userIds.isEmpty()) return 0; Map<User, String> modifiedUsers = updateUserPasswords(userIds, true); // email templates String resetPasswordBodyTemplate = serverConfig.getResetPasswordEmailBodyTemplate(); String resetPasswordSubjectTemplate = serverConfig.getResetPasswordEmailSubjectTemplate(); SimpleTemplateEngine templateEngine = new SimpleTemplateEngine(scripting.getClassLoader()); Map<String, Template> localizedBodyTemplates = new HashMap<>(); Map<String, Template> localizedSubjectTemplates = new HashMap<>(); // load default Template bodyDefaultTemplate = loadDefaultTemplate(resetPasswordBodyTemplate, templateEngine); Template subjectDefaultTemplate = loadDefaultTemplate(resetPasswordSubjectTemplate, templateEngine); for (Map.Entry<User, String> userPasswordEntry : modifiedUsers.entrySet()) { User user = userPasswordEntry.getKey(); if (StringUtils.isNotEmpty(user.getEmail())) { EmailTemplate template = getResetPasswordTemplate(user, templateEngine, resetPasswordSubjectTemplate, resetPasswordBodyTemplate, subjectDefaultTemplate, bodyDefaultTemplate, localizedSubjectTemplates, localizedBodyTemplates); String password = userPasswordEntry.getValue(); sendResetPasswordEmail(user, password, template.getSubjectTemplate(), template.getBodyTemplate()); } } return modifiedUsers.size(); } @Override public Map<UUID, String> changePasswordsAtLogon(List<UUID> userIds, boolean generatePassword) { checkNotNullArgument(userIds, "Null users list"); checkUpdatePermission(User.class); if (userIds.isEmpty()) return Collections.emptyMap(); Map<User, String> modifiedUsers = updateUserPasswords(userIds, generatePassword); Map<UUID, String> userPasswords = new LinkedHashMap<>(); for (Map.Entry<User, String> entry : modifiedUsers.entrySet()) userPasswords.put(entry.getKey().getId(), entry.getValue()); return userPasswords; } @Override public boolean checkPassword(UUID userId, String passwordHash) { checkNotNullArgument(userId, "Null userId"); checkNotNullArgument(passwordHash, "Null new password hash"); User user; Transaction tx = persistence.getTransaction(); try { EntityManager em = persistence.getEntityManager(); user = em.find(User.class, userId, CHECK_PASSWORD_VIEW); if (user == null) throw new RuntimeException("Unable to find user with id: " + userId); tx.commit(); } finally { tx.end(); } return passwordEncryption.checkPassword(user, passwordHash); } @Override public void resetRememberMeTokens(List<UUID> userIds) { Transaction tx = persistence.getTransaction(); try { EntityManager em = persistence.getEntityManager(); Query query = em.createQuery("delete from sec$RememberMeToken rt where rt.user.id in :userIds"); query.setParameter("userIds", userIds); query.executeUpdate(); tx.commit(); } finally { tx.end(); } } @Override public void resetRememberMeTokens() { Transaction tx = persistence.createTransaction(); try { EntityManager em = persistence.getEntityManager(); Query query = em.createQuery("delete from sec$RememberMeToken rt"); query.executeUpdate(); tx.commit(); } finally { tx.end(); } } @Override public String generateRememberMeToken(UUID userId) { String token = RandomStringUtils.randomAlphanumeric(RememberMeToken.TOKEN_LENGTH); Transaction tx = persistence.createTransaction(); try { EntityManager em = persistence.getEntityManager(); RememberMeToken rememberMeToken = metadata.create(RememberMeToken.class); rememberMeToken.setToken(token); rememberMeToken.setUser(em.getReference(User.class, userId)); em.persist(rememberMeToken); tx.commit(); } finally { tx.end(); } return token; } @Override public List<String> getSessionAttributeNames(UUID groupId) { Preconditions.checkNotNullArgument(groupId, "groupId is null"); checkPermission(SessionAttribute.class, EntityOp.READ); checkUpdatePermission(Group.class); checkUpdatePermission(Constraint.class); Set<String> attributes = new HashSet<>(); try (Transaction tx = persistence.createTransaction()) { EntityManager em = persistence.getEntityManager(); Query query = em.createQuery("select a.name from sec$SessionAttribute a where a.group.id = ?1"); query.setParameter(1, groupId); //noinspection unchecked attributes.addAll(query.getResultList()); query = em.createQuery( "select a.name from sec$GroupHierarchy h join h.parent.sessionAttributes a where h.group.id = ?1"); query.setParameter(1, groupId); //noinspection unchecked attributes.addAll(query.getResultList()); tx.commit(); } return new ArrayList<>(attributes); } @Override public UserTimeZone loadOwnTimeZone() { Transaction tx = persistence.createTransaction(); try { EntityManager em = persistence.getEntityManager(); User user = em.find(User.class, userSessionSource.getUserSession().getUser().getId(), "user.timeZone"); if (user == null) throw new EntityAccessException(User.class, userSessionSource.getUserSession().getUser().getId()); tx.commit(); return new UserTimeZone(user.getTimeZone(), Boolean.TRUE.equals(user.getTimeZoneAuto())); } finally { tx.end(); } } @Override public void saveOwnTimeZone(UserTimeZone timeZone) { log.debug("Saving user's time zone settings: " + timeZone); Transaction tx = persistence.createTransaction(); try { EntityManager em = persistence.getEntityManager(); User user = em.find(User.class, userSessionSource.getUserSession().getUser().getId(), "user.timeZone"); if (user == null) throw new EntityAccessException(User.class, userSessionSource.getUserSession().getUser().getId()); user.setTimeZone(timeZone.name); user.setTimeZoneAuto(timeZone.auto); tx.commit(); } finally { tx.end(); } } @Override public String loadOwnLocale() { try (Transaction tx = persistence.createTransaction()) { EntityManager em = persistence.getEntityManager(); User user = em.find(User.class, userSessionSource.getUserSession().getUser().getId(), "user.locale"); if (user == null) throw new EntityAccessException(User.class, userSessionSource.getUserSession().getUser().getId()); tx.commit(); return user.getLanguage(); } } @Override public void saveOwnLocale(String locale) { UUID userId = userSessionSource.getUserSession().getUser().getId(); log.debug("Saving user's {} language settings: {}", userId, locale); try (Transaction tx = persistence.createTransaction()) { EntityManager em = persistence.getEntityManager(); User user = em.find(User.class, userId, "user.locale"); if (user == null) throw new EntityAccessException(User.class, userId); user.setLanguage(locale); tx.commit(); } } @Override public void changeUserPassword(UUID userId, String newPasswordHash) { Transaction tx = persistence.createTransaction(); try { EntityManager em = persistence.getEntityManager(); User user = em.find(User.class, userId, CHANGE_PASSWORD_VIEW); if (user == null) { throw new EntityAccessException(User.class, userId); } user.setPassword(newPasswordHash); user.setChangePasswordAtNextLogon(false); // reset remember me for user Query query = em.createQuery("delete from sec$RememberMeToken rt where rt.user.id=:userId"); query.setParameter("userId", userId); query.executeUpdate(); tx.commit(); } finally { tx.end(); } } protected EmailTemplate getResetPasswordTemplate(User user, SimpleTemplateEngine templateEngine, String resetPasswordSubjectTemplate, String resetPasswordBodyTemplate, Template subjectDefaultTemplate, Template bodyDefaultTemplate, Map<String, Template> localizedSubjectTemplates, Map<String, Template> localizedBodyTemplates) { boolean userLocaleIsUnknown = StringUtils.isEmpty(user.getLanguage()); String locale = userLocaleIsUnknown ? messageTools.getDefaultLocale().getLanguage() : user.getLanguage(); Template bodyTemplate; if (userLocaleIsUnknown) { bodyTemplate = bodyDefaultTemplate; } else { if (localizedBodyTemplates.containsKey(locale)) bodyTemplate = localizedBodyTemplates.get(locale); else { String templateString = getLocalizedTemplateContent(resetPasswordBodyTemplate, locale); if (templateString == null) { log.warn("Reset passwords: Not found email body template for locale: '{}'", locale); bodyTemplate = bodyDefaultTemplate; } else { bodyTemplate = getTemplate(templateEngine, templateString); } localizedBodyTemplates.put(locale, bodyTemplate); } } Template subjectTemplate; if (userLocaleIsUnknown) { subjectTemplate = subjectDefaultTemplate; } else { if (localizedSubjectTemplates.containsKey(locale)) subjectTemplate = localizedSubjectTemplates.get(locale); else { String templateString = getLocalizedTemplateContent(resetPasswordSubjectTemplate, locale); if (templateString == null) { log.warn("Reset passwords: Not found email subject template for locale '{}'", locale); subjectTemplate = subjectDefaultTemplate; } else { subjectTemplate = getTemplate(templateEngine, templateString); } localizedSubjectTemplates.put(locale, subjectTemplate); } } return new EmailTemplate(subjectTemplate, bodyTemplate); } private String getLocalizedTemplateContent(String defaultTemplateName, String locale) { String localizedTemplate = FilenameUtils.getFullPath(defaultTemplateName) + FilenameUtils.getBaseName(defaultTemplateName) + "_" + locale + "." + FilenameUtils.getExtension(defaultTemplateName); return resources.getResourceAsString(localizedTemplate); } protected Template getTemplate(SimpleTemplateEngine templateEngine, String templateString) { Template bodyTemplate; try { bodyTemplate = templateEngine.createTemplate(templateString); } catch (Exception e) { throw new RuntimeException("Unable to compile Groovy template", e); } return bodyTemplate; } protected Template loadDefaultTemplate(String templatePath, SimpleTemplateEngine templateEngine) { String defaultTemplateContent = resources.getResourceAsString(templatePath); if (defaultTemplateContent == null) { throw new IllegalStateException("Not found default email template for reset passwords operation"); } //noinspection UnnecessaryLocalVariable Template template = getTemplate(templateEngine, defaultTemplateContent); return template; } protected void sendResetPasswordEmail(User user, String password, Template subjectTemplate, Template bodyTemplate) { Transaction tx = persistence.getTransaction(); String emailBody; String emailSubject; try { Map<String, Object> binding = new HashMap<>(); binding.put("user", user); binding.put("password", password); binding.put("persistence", persistence); emailBody = bodyTemplate.make(binding).writeTo(new StringWriter(0)).toString(); emailSubject = subjectTemplate.make(binding).writeTo(new StringWriter(0)).toString(); tx.commit(); } catch (IOException e) { throw new RuntimeException("Unable to write Groovy template content", e); } finally { tx.end(); } EmailInfo emailInfo = new EmailInfo(user.getEmail(), emailSubject, emailBody); emailerAPI.sendEmailAsync(emailInfo); } protected Map<User, String> updateUserPasswords(List<UUID> userIds, boolean generatePassword) { Map<User, String> modifiedUsers = new LinkedHashMap<>(); Transaction tx = persistence.getTransaction(); try { EntityManager em = persistence.getEntityManager(); TypedQuery<User> query = em.createQuery("select u from sec$User u where u.id in :userIds", User.class); query.setParameter("userIds", userIds); query.setViewName(RESET_PASSWORD_VIEW); List<User> users = query.getResultList(); if (users == null || users.size() != userIds.size()) throw new IllegalStateException("Not all users found in database"); for (User user : users) { String password = null; if (generatePassword) { password = passwordEncryption.generateRandomPassword(); String passwordHash = passwordEncryption.getPasswordHash(user.getId(), password); user.setPassword(passwordHash); } user.setChangePasswordAtNextLogon(true); modifiedUsers.put(user, password); } resetRememberMeTokens(userIds); tx.commit(); } finally { tx.end(); } return modifiedUsers; } protected Role cloneRole(Role role, Set<String> roleNames, EntityManager em) { Role roleClone = metadata.create(Role.class); String newRoleName = generateName(role.getName(), roleNames); roleClone.setName(newRoleName); roleClone.setType(role.getType()); roleClone.setDefaultRole(role.getDefaultRole()); roleClone.setLocName(role.getLocName()); roleClone.setDescription(role.getDescription()); em.persist(roleClone); if (role.getPermissions() != null) { for (Permission permission : role.getPermissions()) { Permission permissionClone = clonePermission(permission, roleClone); em.persist(permissionClone); } } return roleClone; } protected Group cloneGroup(Group group, Group parent, Set<String> groupNames, EntityManager em) { Group groupClone = metadata.create(Group.class); String newGroupName = generateName(group.getName(), groupNames); groupClone.setName(newGroupName); groupNames.add(newGroupName); groupClone.setParent(parent); em.persist(groupClone); // fire hierarchy listeners em.flush(); if (group.getConstraints() != null) { for (Constraint constraint : group.getConstraints()) { Constraint constraintClone = cloneConstraint(constraint, groupClone); em.persist(constraintClone); } } if (group.getSessionAttributes() != null) { for (SessionAttribute attribute : group.getSessionAttributes()) { SessionAttribute attributeClone = cloneSessionAttribute(attribute, groupClone); em.persist(attributeClone); } } TypedQuery<Group> query = em.createQuery("select g from sec$Group g where g.parent.id = :group", Group.class); query.setParameter("group", group); List<Group> subGroups = query.getResultList(); if (subGroups != null && subGroups.size() > 0) { for (Group subGroup : subGroups) { cloneGroup(subGroup, groupClone, groupNames, em); } } return groupClone; } protected String generateName(String originalGroupName, Set<String> groupNames) { String newGroupName; int i = 1; do { newGroupName = originalGroupName + " (" + i + ")"; i++; } while (groupNames.contains(newGroupName)); return newGroupName; } protected SessionAttribute cloneSessionAttribute(SessionAttribute attribute, Group group) { SessionAttribute resultAttribute = metadata.create(SessionAttribute.class); resultAttribute.setName(attribute.getName()); resultAttribute.setDatatype(attribute.getDatatype()); resultAttribute.setStringValue(attribute.getStringValue()); resultAttribute.setGroup(group); return resultAttribute; } protected Constraint cloneConstraint(Constraint constraint, Group group) { Constraint resultConstraint = metadata.create(Constraint.class); resultConstraint.setEntityName(constraint.getEntityName()); resultConstraint.setCode(constraint.getCode()); resultConstraint.setCheckType(constraint.getCheckType()); resultConstraint.setOperationType(constraint.getOperationType()); resultConstraint.setJoinClause(constraint.getJoinClause()); resultConstraint.setWhereClause(constraint.getWhereClause()); resultConstraint.setGroovyScript(constraint.getGroovyScript()); resultConstraint.setFilterXml(constraint.getFilterXml()); resultConstraint.setIsActive(constraint.getIsActive()); resultConstraint.setGroup(group); return resultConstraint; } protected Permission clonePermission(Permission permission, Role role) { Permission resultPermission = metadata.create(Permission.class); resultPermission.setValue(permission.getValue()); resultPermission.setType(permission.getType()); resultPermission.setTarget(permission.getTarget()); resultPermission.setRole(role); return resultPermission; } /** * Template pair : subject + body */ protected static class EmailTemplate { private Template subjectTemplate; private Template bodyTemplate; private EmailTemplate(Template subjectTemplate, Template bodyTemplate) { this.subjectTemplate = subjectTemplate; this.bodyTemplate = bodyTemplate; } public Template getSubjectTemplate() { return subjectTemplate; } public Template getBodyTemplate() { return bodyTemplate; } } @Override public boolean isUsersRemovingAllowed(Collection<String> userLogins) { return !userLogins.contains(serverConfig.getJmxUserLogin()) && !userLogins.contains(serverConfig.getAnonymousLogin()); } }