Commit ae7476d2 authored by Matija Obreza's avatar Matija Obreza
Browse files

User management API v1

- with Unit tests
parent f7430b7b
......@@ -18,6 +18,7 @@ package org.genesys2.server.api.v0;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.security.NoUserFoundException;
......@@ -145,7 +146,7 @@ public class UsersController extends ApiBaseController {
}
@RequestMapping("/user/{uuid:.+}/vetted-user")
public void addRoleVettedUser(@PathVariable("uuid") String uuid) {
public void addRoleVettedUser(@PathVariable("uuid") UUID uuid) {
userService.addVettedUserRole(uuid);
}
......@@ -158,7 +159,7 @@ public class UsersController extends ApiBaseController {
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping(value = "/user/{uuid}/enabled", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public void changeEnabled(@PathVariable("uuid") String uuid, @RequestBody boolean enabled) {
public void changeEnabled(@PathVariable("uuid") UUID uuid, @RequestBody boolean enabled) throws UserException {
userService.setAccountActive(uuid, enabled);
}
......
/*
* Copyright 2018 Global Crop Diversity Trust
*
* 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 org.genesys2.server.api.v1;
import java.io.IOException;
import java.util.Random;
import java.util.UUID;
import org.genesys.blocks.security.NoUserFoundException;
import org.genesys.blocks.security.NotUniqueUserException;
import org.genesys.blocks.security.UserException;
import org.genesys.catalog.api.FilteredPage;
import org.genesys.catalog.service.ShortFilterService;
import org.genesys.catalog.service.ShortFilterService.FilterInfo;
import org.genesys2.server.api.ApiBaseController;
import org.genesys2.server.api.Pagination;
import org.genesys2.server.exception.NotFoundElement;
import org.genesys2.server.model.impl.User;
import org.genesys2.server.service.EMailVerificationService;
import org.genesys2.server.service.UserService;
import org.genesys2.server.service.filter.UserFilter;
import org.genesys2.util.RandomPasswordUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.hazelcast.util.CollectionUtil;
import io.swagger.annotations.Api;
/**
* User API v1
*
* @author Matija Obreza
*/
@RestController("userApi1")
@RequestMapping(UserManagementController.CONTROLLER_URL)
@PreAuthorize("isAuthenticated()")
@Api(tags = { "user" })
public class UserManagementController {
/** The Constant API_BASE. */
public static final String CONTROLLER_URL = ApiBaseController.APIv1_BASE + "/user";
/** The Constant LOG. */
private static final Logger LOG = LoggerFactory.getLogger(UserManagementController.class);
/** The short filter service. */
@Autowired
protected ShortFilterService shortFilterService;
/** The repository service. */
@Autowired
protected UserService userService;
@Autowired
private EMailVerificationService emailVerificationService;
/**
* Gets the user.
*
* @param uuid the uuid
* @return the user
*/
@GetMapping(value = "/u/{uuid}")
public User getUser(@PathVariable("uuid") final UUID uuid) {
User user = userService.getUser(uuid);
if (user == null) {
throw new NotFoundElement("No such user");
}
return user;
}
/**
* Unlock account.
*
* @param uuid the uuid
* @return the user
* @throws NoUserFoundException the no user found exception
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PostMapping(value = "/u/{uuid}/unlock")
public User unlockAccount(@PathVariable("uuid") final UUID uuid) throws NoUserFoundException {
User user = userService.getUser(uuid);
userService.setAccountLock(user.getId(), false);
return userService.getUser(uuid);
}
/**
* Lock account.
*
* @param uuid the uuid
* @return the user
* @throws NoUserFoundException the no user found exception
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PostMapping(value = "/u/{uuid}/lock")
public User lockAccount(@PathVariable("uuid") final UUID uuid) throws NoUserFoundException {
User user = userService.getUser(uuid);
userService.setAccountLock(user.getId(), true);
return userService.getUser(uuid);
}
/**
* Enable account.
*
* @param uuid the uuid
* @return the user
* @throws NoUserFoundException the no user found exception
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PostMapping(value = "/u/{uuid}/enable")
public User enableAccount(@PathVariable("uuid") final UUID uuid) throws UserException {
userService.setAccountActive(uuid, true);
return userService.getUser(uuid);
}
/**
* Disable account.
*
* @param uuid the uuid
* @return the user
* @throws NoUserFoundException the no user found exception
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PostMapping(value = "/u/{uuid}/disable")
public User disableAccount(@PathVariable("uuid") final UUID uuid) throws UserException {
userService.setAccountActive(uuid, false);
return userService.getUser(uuid);
}
/**
* Archive account.
*
* @param uuid the uuid
* @return the user
* @throws UserException the user exception
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@PostMapping(value = "/u/{uuid}/archive")
public User archiveAccount(@PathVariable("uuid") final UUID uuid) throws UserException {
User user = userService.getUser(uuid);
userService.archiveUser(user);
LOG.info("Archived user " + user.getEmail());
return user;
}
/**
* Archive account.
*
* @param uuid the uuid
* @return the user
* @throws UserException the user exception
*/
@PostMapping(value = "/u/{uuid}/ftp-password")
public String generateFtpPassword(@PathVariable("uuid") final UUID uuid) throws UserException {
User user = getUser(uuid);
String generatedPassword = RandomPasswordUtil.generatePassword(new Random(), 15);
userService.setFtpPassword(user, generatedPassword);
LOG.info("Generated new FTP password for user " + user.getEmail());
return generatedPassword;
}
/**
* Send verification email.
*
* @param uuid the uuid
*/
@PostMapping(value = "/u/{uuid}/email-verification")
public boolean sendEmail(@PathVariable("uuid") UUID uuid) {
final User user = getUser(uuid);
emailVerificationService.sendVerificationEmail(user);
return true;
}
/**
* Update user.
*
* @param user the user
* @return the user
* @throws NotUniqueUserException the not unique user exception
* @throws UserException the user exception
*/
@PutMapping(value = "/user")
@PreAuthorize("hasRole('ADMINISTRATOR')")
public User updateUser(@RequestBody User user) throws NotUniqueUserException, UserException {
User updated = userService.updateUser(user, user.getEmail(), user.getFullName());
if (CollectionUtil.isNotEmpty(user.getRoles())) {
return userService.setRoles(updated, user.getRoles());
} else {
return updated;
}
}
/**
* List users.
*
* @param filterCode the filter code
* @param page the page
* @param filter the filter
* @return the filtered page
* @throws IOException Signals that an I/O exception has occurred.
*/
@PostMapping(value = "/list")
@PreAuthorize("hasRole('ADMINISTRATOR')")
public FilteredPage<User> listUsers(@RequestParam(name = "f", required = false) String filterCode, final Pagination page,
@RequestBody(required = false) UserFilter filter) throws IOException {
FilterInfo<UserFilter> filterInfo = shortFilterService.processFilter(filterCode, filter, UserFilter.class);
return new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, userService.list(filterInfo.filter, page.toPageRequest(100, Sort.Direction.ASC, "email")));
}
}
......@@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
......@@ -98,7 +99,7 @@ public class UserProfileController extends BaseController {
@RequestMapping("/{uuid:.+}/vetted-user")
@PreAuthorize("hasRole('ADMINISTRATOR')")
public String addRoleVettedUser(@PathVariable("uuid") String uuid) {
public String addRoleVettedUser(@PathVariable("uuid") UUID uuid) {
userService.addVettedUserRole(uuid);
return "redirect:/profile/" + uuid;
}
......
......@@ -17,6 +17,7 @@
package org.genesys2.server.mvc.admin;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.security.NotUniqueUserException;
......@@ -68,7 +69,7 @@ public class UserProfileController extends BaseController {
}
@RequestMapping("/{uuid:.+}/vetted-user")
public String addRoleVettedUser(@PathVariable("uuid") String uuid) {
public String addRoleVettedUser(@PathVariable("uuid") UUID uuid) {
userService.addVettedUserRole(uuid);
return "redirect:" + URLBASE + uuid;
}
......
......@@ -22,10 +22,11 @@ import org.genesys2.server.model.impl.User;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public interface UserRepository extends JpaRepository<User, Long> {
public interface UserRepository extends JpaRepository<User, Long>, QueryDslPredicateExecutor<User> {
@Query("select u from User u where lower(u.email) = lower(?1)")
User findByEmail(String email);
......
......@@ -26,6 +26,7 @@ import org.genesys.blocks.security.service.PasswordPolicy.PasswordPolicyExceptio
import org.genesys2.server.model.UserRole;
import org.genesys2.server.model.impl.User;
import org.genesys2.server.model.wrapper.UserWrapper;
import org.genesys2.server.service.filter.UserFilter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
......@@ -39,6 +40,7 @@ public interface UserService extends BasicUserService<UserRole, User> {
@PreAuthorize("isAuthenticated()")
User getMe();
@Deprecated
User getUserByUuid(String uuid);
User getUser(UUID uuid);
......@@ -51,13 +53,15 @@ public interface UserService extends BasicUserService<UserRole, User> {
Page<User> listUsers(Pageable pageable);
Page<User> list(UserFilter filter, Pageable pageable);
boolean checkPasswordsMatch(String rawPassword, String encodedPassword);
void setAccountActive(String uuid, boolean enabled);
void setAccountActive(UUID uuid, boolean enabled) throws UserException;
void userEmailValidated(String uuid);
void userEmailValidated(UUID uuid);
void addVettedUserRole(String uuid);
void addVettedUserRole(UUID uuid);
UserDetails getUserDetails(User user);
......
/*
* Copyright 2018 Global Crop Diversity Trust
*
* 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 org.genesys2.server.service.filter;
import java.util.Date;
import java.util.Set;
import org.genesys.blocks.model.filters.AuditedVersionedModelFilter;
import org.genesys.blocks.model.filters.StringFilter;
import org.genesys2.server.model.UserRole;
import org.genesys2.server.model.impl.QUser;
import org.genesys2.server.model.impl.User;
import com.hazelcast.util.CollectionUtil;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
/**
* Filters for {@link User}.
*
* @author Matija Obreza
*/
public class UserFilter extends AuditedVersionedModelFilter<UserFilter, User> {
/** The language. */
public Set<String> uuid;
/** The title. */
public StringFilter email;
/** The role. */
public Set<UserRole> role;
/** The locked. */
public Boolean locked;
/** The enabled. */
public Boolean enabled;
/** The expired. */
public Boolean expired;
/* (non-Javadoc)
* @see org.genesys.blocks.model.filters.BasicModelFilter#buildQuery()
*/
@Override
public Predicate buildQuery() {
return buildQuery(QUser.user);
}
/**
* Builds the query.
*
* @param user the user
* @return the predicate
*/
public Predicate buildQuery(QUser user) {
final BooleanBuilder and = new BooleanBuilder();
super.buildQuery(user, user._super._super._super, and);
if (CollectionUtil.isNotEmpty(uuid)) {
and.and(user.uuid.in(uuid));
}
if (email != null) {
and.and(email.buildQuery(user.email));
}
if (CollectionUtil.isNotEmpty(role)) {
and.and(user.roles.any().in(role));
}
if (enabled != null) {
and.and(user.active.eq(enabled.booleanValue()));
}
if (locked != null) {
if (locked.booleanValue()) {
and.and(user.lockedUntil.gt(new Date()));
} else {
and.and(user.lockedUntil.loe(new Date())).or(user.lockedUntil.isNull());
}
}
if (expired != null) {
if (expired.booleanValue()) {
and.and(user.accountExpires.loe(new Date()));
} else {
and.and(user.accountExpires.gt(new Date())).or(user.accountExpires.isNull());
}
}
return and;
}
}
......@@ -18,6 +18,7 @@ package org.genesys2.server.service.impl;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.UUID;
import org.genesys.blocks.security.SecurityContextUtil;
import org.genesys.blocks.security.UserException;
......@@ -111,7 +112,7 @@ public class EMailVerificationServiceImpl implements EMailVerificationService {
@Transactional
public void validateEMail(String tokenUuid, String key) throws NoSuchVerificationTokenException, TokenExpiredException {
final VerificationToken consumedToken = tokenVerificationService.consumeToken("email-verification", tokenUuid, key);
userService.userEmailValidated(consumedToken.getData());
userService.userEmailValidated(UUID.fromString(consumedToken.getData()));
}
/**
......
/*
* Copyright 2017 Global Crop Diversity Trust
* Copyright 2018 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -12,7 +12,7 @@
* 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 org.genesys2.server.service.impl;
......@@ -42,6 +42,7 @@ import org.genesys2.server.model.impl.User;
import org.genesys2.server.model.wrapper.UserWrapper;
import org.genesys2.server.persistence.UserRepository;
import org.genesys2.server.service.UserService;
import org.genesys2.server.service.filter.UserFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -64,6 +65,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Sets;
import com.querydsl.core.BooleanBuilder;
@Service(value = "userService")
@Transactional(readOnly = true)
......@@ -228,13 +230,13 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR')")
public void setAccountActive(String uuid, boolean enabled) {
final User user = userRepository.findByUuid(uuid);
public void setAccountActive(UUID uuid, boolean enabled) throws UserException {
final User user = userRepository.findByUuid(uuid.toString());
if (!enabled && user.getRoles().contains(UserRole.ADMINISTRATOR)) {
throw new SecurityException("Can't disable ADMINISTRATOR accounts");
}
user.setActive(enabled);
userRepository.save(user);
updateUser(user);
LOG.warn("User account for user={} enabled={}", user.getEmail(), enabled);
}
......@@ -308,10 +310,10 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
@Override
@Transactional
public void userEmailValidated(String uuid) {
public void userEmailValidated(UUID uuid) {
LOG.info("Validating user email for uuid={}", uuid);
final User user = userRepository.findByUuid(uuid);
final User user = userRepository.findByUuid(uuid.toString());
Set<UserRole> userRoles = user.getRoles();
if (userRoles == null) {
LOG.debug("User roles are null, creating role set");
......@@ -331,8 +333,8 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
@Override
@Transactional
public void addVettedUserRole(String uuid) {
final User user = userRepository.findByUuid(uuid);
public void addVettedUserRole(UUID uuid) {
final User user = userRepository.findByUuid(uuid.toString());
final Set<UserRole> userRoles = user.getRoles();
userRoles.add(UserRole.VETTEDUSER);
......@@ -435,8 +437,11 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
userRepository.save(u);
}
/* (non-Javadoc)
* @see org.genesys2.server.service.UserService#archiveUser(org.genesys2.server.model.impl.User)
*/
@Override
@Transactional
@Transactional
public void archiveUser(User user) throws UserException {
user = userRepository.findOne(user.getId());
......@@ -458,9 +463,19 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
user.setPasswordExpires(now);
user.getRoles().clear();
userRepository.save(user);
updateUser(user);
LOG.warn("Removing ACL entries for {}", user.getEmail());
aclEntryRepository.delete(user.getAclEntries());
}
/* (non-Javadoc)
* @see org.genesys2.server.service.UserService#list(org.genesys2.server.service.filter.UserFilter, org.springframework.data.domain.Pageable)
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
@Override
public Page<User> list(UserFilter filter, Pageable page) {
Page<User> res = userRepository.findAll(new BooleanBuilder().and(filter.buildQuery()), page);
return new PageImpl<>(res.getContent(), page, res.getTotalElements());
}
}
......@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;
......@@ -159,7 +160,7 @@ public class GoogleOAuthUtil {
} catch (UsernameNotFoundException e) {
LOG.info("Username not found, creating new Google account");
user = userService.createUser(userInfo.getAccountEmail(), userInfo.getDisplayName(),null, BasicUser.AccountType.GOOGLE);
userService.userEmailValidated(user.getUuid());
userService.userEmailValidated(UUID.fromString(user.getUuid()));
}
return user;