Java tutorial
/** * This file is part of alf.io. * * alf.io is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * alf.io 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with alf.io. If not, see <http://www.gnu.org/licenses/>. */ package alfio.manager.user; import alfio.model.result.ValidationResult; import alfio.model.user.*; import alfio.model.user.join.UserOrganization; import alfio.repository.InvoiceSequencesRepository; import alfio.repository.user.AuthorityRepository; import alfio.repository.user.OrganizationRepository; import alfio.repository.user.UserRepository; import alfio.repository.user.join.UserOrganizationRepository; import alfio.util.PasswordGenerator; import ch.digitalfondue.npjt.AffectedRowCountAndKey; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; @Component @RequiredArgsConstructor public class UserManager { public static final String ADMIN_USERNAME = "admin"; private final AuthorityRepository authorityRepository; private final OrganizationRepository organizationRepository; private final UserOrganizationRepository userOrganizationRepository; private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final InvoiceSequencesRepository invoiceSequencesRepository; private List<Authority> getUserAuthorities(User user) { return authorityRepository.findGrantedAuthorities(user.getUsername()); } public List<UserWithOrganizations> findAllUsers(String username) { List<Organization> organizations = findUserOrganizations(username); Predicate<Collection<?>> isNotEmpty = ks -> !ks.isEmpty(); return Optional.of(organizations).filter(isNotEmpty).flatMap(org -> { Map<Integer, List<UserOrganization>> usersAndOrganizations = userOrganizationRepository .findByOrganizationIdsOrderByUserId( organizations.stream().map(Organization::getId).collect(toList())) .stream().collect(Collectors.groupingBy(UserOrganization::getUserId)); return Optional.of(usersAndOrganizations.keySet()).filter(isNotEmpty) .map(ks -> userRepository.findByIds(ks).stream().map(u -> { List<UserOrganization> userOrganizations = usersAndOrganizations.get(u.getId()); return new UserWithOrganizations(u, organizations.stream().filter( o -> userOrganizations.stream().anyMatch(uo -> uo.getOrganizationId() == o.getId())) .collect(toList())); }).collect(toList())); }).orElseGet(Collections::emptyList); } public List<User> findAllEnabledUsers(String username) { return findUserOrganizations(username).stream() .flatMap(o -> userOrganizationRepository.findByOrganizationId(o.getId()).stream()) .map(uo -> userRepository.findById(uo.getUserId())).filter(User::isEnabled).collect(toList()); } public User findUserByUsername(String username) { return userRepository.findEnabledByUsername(username).orElseThrow(IllegalArgumentException::new); } public boolean usernameExists(String username) { return userRepository.findIdByUserName(username).isPresent(); } public User findUser(int id) { return userRepository.findById(id); } public Collection<Role> getAvailableRoles(String username) { User user = findUserByUsername(username); return isAdmin(user) || isOwner(user) ? EnumSet.of(Role.OWNER, Role.OPERATOR, Role.SUPERVISOR, Role.SPONSOR, Role.API_CONSUMER) : Collections.emptySet(); } /** * Return the most privileged role of a user * @param user * @return user role */ public Role getUserRole(User user) { return getUserAuthorities(user).stream().map(Authority::getRole).sorted().findFirst().orElse(Role.OPERATOR); } public List<Organization> findUserOrganizations(String username) { return organizationRepository.findAllForUser(username); } public Organization findOrganizationById(int id, String username) { return findUserOrganizations(username).stream().filter(o -> o.getId() == id).findFirst() .orElseThrow(IllegalArgumentException::new); } public boolean isAdmin(User user) { return checkRole(user, Collections.singleton(Role.ADMIN)); } public boolean isOwner(User user) { return checkRole(user, EnumSet.of(Role.ADMIN, Role.OWNER)); } public boolean isOwnerOfOrganization(User user, int organizationId) { return isAdmin(user) || (isOwner(user) && userOrganizationRepository.findByUserId(user.getId()).stream() .anyMatch(uo -> uo.getOrganizationId() == organizationId)); } private boolean checkRole(User user, Set<Role> expectedRoles) { Set<String> roleNames = expectedRoles.stream().map(Role::getRoleName).collect(Collectors.toSet()); return authorityRepository.checkRole(user.getUsername(), roleNames); } @Transactional public int createOrganization(String name, String description, String email) { organizationRepository.create(name, description, email); int orgId = organizationRepository.getIdByName(name); invoiceSequencesRepository.initFor(orgId); return orgId; } @Transactional public void updateOrganization(Integer id, String name, String email, String description) { organizationRepository.update(id, name, description, email); } public ValidationResult validateOrganization(Integer id, String name, String email, String description) { if (organizationRepository.findByName(name).isPresent()) { return ValidationResult.failed(new ValidationResult.ErrorDescriptor("name", "There is already another organization with the same name.")); } Validate.notBlank(name, "name can't be empty"); Validate.notBlank(email, "email can't be empty"); Validate.notBlank(description, "description can't be empty"); return ValidationResult.success(); } @Transactional public void editUser(int id, int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, String currentUsername) { boolean admin = ADMIN_USERNAME.equals(username) && Role.ADMIN == role; if (!admin) { int userOrganizationResult = userOrganizationRepository.updateUserOrganization(id, organizationId); Assert.isTrue(userOrganizationResult == 1, "unexpected error during organization update"); } int userResult = userRepository.update(id, username, firstName, lastName, emailAddress); Assert.isTrue(userResult == 1, "unexpected error during user update"); if (!admin) { Assert.isTrue(getAvailableRoles(currentUsername).contains(role), "cannot assign role " + role); authorityRepository.revokeAll(username); authorityRepository.create(username, role.getRoleName()); } } @Transactional public UserWithPassword insertUser(int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, User.Type userType) { String userPassword = PasswordGenerator.generateRandomPassword(); return insertUser(organizationId, username, firstName, lastName, emailAddress, role, userType, userPassword); } @Transactional public UserWithPassword insertUser(int organizationId, String username, String firstName, String lastName, String emailAddress, Role role, User.Type userType, String userPassword) { Organization organization = organizationRepository.getById(organizationId); AffectedRowCountAndKey<Integer> result = userRepository.create(username, passwordEncoder.encode(userPassword), firstName, lastName, emailAddress, true, userType); userOrganizationRepository.create(result.getKey(), organization.getId()); authorityRepository.create(username, role.getRoleName()); return new UserWithPassword(userRepository.findById(result.getKey()), userPassword, UUID.randomUUID().toString()); } @Transactional public UserWithPassword resetPassword(int userId) { User user = findUser(userId); String password = PasswordGenerator.generateRandomPassword(); Validate.isTrue(userRepository.resetPassword(userId, passwordEncoder.encode(password)) == 1, "error during password reset"); return new UserWithPassword(user, password, UUID.randomUUID().toString()); } @Transactional public boolean updatePassword(String username, String newPassword) { User user = userRepository.findByUsername(username).orElseThrow(IllegalStateException::new); Validate.isTrue(PasswordGenerator.isValid(newPassword), "invalid password"); Validate.isTrue(userRepository.resetPassword(user.getId(), passwordEncoder.encode(newPassword)) == 1, "error during password update"); return true; } @Transactional public void deleteUser(int userId, String currentUsername) { User currentUser = userRepository.findEnabledByUsername(currentUsername) .orElseThrow(IllegalArgumentException::new); Assert.isTrue(userId != currentUser.getId(), "sorry but you cannot commit suicide"); userRepository.deleteUserFromSponsorScan(userId); userRepository.deleteUserFromOrganization(userId); userRepository.deleteUser(userId); } @Transactional public void enable(int userId, String currentUsername, boolean status) { User currentUser = userRepository.findEnabledByUsername(currentUsername) .orElseThrow(IllegalArgumentException::new); Assert.isTrue(userId != currentUser.getId(), "sorry but you cannot commit suicide"); userRepository.toggleEnabled(userId, status); } public ValidationResult validateUser(Integer id, String username, int organizationId, String role, String firstName, String lastName, String emailAddress) { if (userRepository.findByUsername(username).isPresent()) { return ValidationResult.failed(new ValidationResult.ErrorDescriptor("username", "There is already another user with the same username.")); } return ValidationResult.of(Stream .of(Pair.of(firstName, "firstName"), Pair.of(lastName, "lastName"), Pair.of(emailAddress, "emailAddress")) .filter(p -> StringUtils.isEmpty(p.getKey())) .map(p -> new ValidationResult.ErrorDescriptor(p.getKey(), p.getValue() + " is required")) .collect(toList())); } public ValidationResult validateNewPassword(String username, String oldPassword, String newPassword, String newPasswordConfirm) { return userRepository.findByUsername(username).map(u -> { List<ValidationResult.ErrorDescriptor> errors = new ArrayList<>(); Optional<String> password = userRepository.findPasswordByUsername(username); if (!password.filter(p -> passwordEncoder.matches(oldPassword, p)).isPresent()) { errors.add(new ValidationResult.ErrorDescriptor("alfio.old-password-invalid", "wrong password")); } if (!PasswordGenerator.isValid(newPassword)) { errors.add(new ValidationResult.ErrorDescriptor("alfio.new-password-invalid", "new password is not strong enough")); } if (!StringUtils.equals(newPassword, newPasswordConfirm)) { errors.add(new ValidationResult.ErrorDescriptor("alfio.new-password-does-not-match", "new password has not been confirmed")); } return ValidationResult.of(errors); }).orElseGet(ValidationResult::failed); } public List<Integer> disableAccountsOlderThan(Date date, User.Type type) { List<Integer> userIds = userRepository.findUserToDisableOlderThan(date, type); if (!userIds.isEmpty()) { userRepository.disableAccountsOlderThan(date, type); } return userIds; } }