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

Merge branch '135-user-administration-api' into 'master'

Resolve "User administration API"

Closes #135

See merge request !130
parents ec6fc69b 414e2418
/*
* 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.
......@@ -21,13 +21,14 @@ import org.genesys.catalog.model.user.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.stereotype.Repository;
/**
* The Interface UserRepository.
*/
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
public interface UserRepository extends JpaRepository<User, Long>, QueryDslPredicateExecutor<User> {
/**
* Find by email.
......@@ -38,6 +39,15 @@ public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where lower(u.email) = lower(?1)")
User findByEmail(String email);
/**
* Find by uuid and version.
*
* @param uuid the uuid
* @param version the version
* @return the user
*/
User getByUuidAndVersion(String uuid, int version);
/**
* Autocomplete user by email or fullName
*
......@@ -48,4 +58,13 @@ public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where lower(u.email) like concat(lower(?1), '%') or lower(u.fullName) like concat(lower(?1), '%')")
List<User> autocomplete(String term, Pageable page);
/**
* Find by uuid.
*
* @param string the uuid
* @return the user
*/
// FIXME User#uuid should be UUID
User findByUuid(String string);
}
......@@ -33,6 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.multipart.MultipartFile;
......@@ -108,6 +109,7 @@ public interface DatasetService {
* @param input query by example
* @return dataset loaded from the database
*/
@PostAuthorize("returnObject==null || returnObject.published || hasPermission(returnObject, 'read')")
Dataset loadDataset(Dataset input);
/**
......@@ -117,6 +119,7 @@ public interface DatasetService {
* @param version the version
* @return the dataset
*/
@PostAuthorize("returnObject==null || returnObject.published || hasPermission(returnObject, 'read')")
Dataset loadDataset(UUID uuid, int version);
/**
......@@ -152,6 +155,7 @@ public interface DatasetService {
* @return loaded dataset
* @throws NotFoundElement when dataset not found by uuid
*/
@PostAuthorize("returnObject==null || returnObject.published || hasPermission(returnObject, 'read')")
Dataset loadDataset(UUID uuid) throws NotFoundElement;
/**
......@@ -188,6 +192,7 @@ public interface DatasetService {
* @return loaded list of RepositoryFile
* @throws NotFoundElement the not found element
*/
@PostAuthorize("#dataset.published || hasPermission(#dataset, 'read')")
List<RepositoryFile> listDatasetFiles(Dataset dataset) throws NotFoundElement;
/**
......
......@@ -140,7 +140,7 @@ public interface DescriptorListService {
* @param descriptorList descriptorList that be deleted
* @return deleted descriptorList
*/
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#descriptor, 'DELETE')")
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#descriptorList, 'DELETE')")
DescriptorList deleteDescriptorList(DescriptorList descriptorList);
/**
......@@ -150,7 +150,7 @@ public interface DescriptorListService {
* @param descriptorList an unpublished descriptor list
* @return published descriptor list
*/
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#descriptor, 'WRITE')")
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#descriptorList, 'WRITE')")
DescriptorList publishDescriptorList(DescriptorList descriptorList);
/**
......@@ -169,7 +169,7 @@ public interface DescriptorListService {
* @param descriptors the descriptors
* @return the descriptor list
*/
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#descriptor, 'WRITE')")
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#descriptorList, 'WRITE')")
DescriptorList setDescriptors(DescriptorList descriptorList, Descriptor[] descriptors);
}
......@@ -63,7 +63,7 @@ public interface DescriptorService {
* @param uuid uuid of descriptor
* @return loaded descriptor
*/
@PostAuthorize("returnObject==null || returnObject.published || hasPermission(returnObject, 'write')")
@PostAuthorize("returnObject==null || returnObject.published || hasPermission(returnObject, 'read')")
Descriptor getDescriptor(UUID uuid);
/**
......@@ -73,7 +73,7 @@ public interface DescriptorService {
* @param version the version
* @return the descriptor
*/
@PostAuthorize("returnObject==null || returnObject.published || hasPermission(returnObject, 'write')")
@PostAuthorize("returnObject==null || returnObject.published || hasPermission(returnObject, 'read')")
Descriptor getDescriptor(UUID uuid, int version);
/**
......@@ -109,6 +109,7 @@ public interface DescriptorService {
* @param source the source
* @return list of updated descriptors
*/
@PreAuthorize("hasRole('ADMINISTRATOR'")
List<Descriptor> upsertDescriptors(List<Descriptor> source);
/**
......
......@@ -16,16 +16,30 @@
package org.genesys.catalog.service;
import java.util.List;
import java.util.UUID;
import org.genesys.blocks.security.service.BasicUserService;
import org.genesys.catalog.model.user.User;
import org.genesys.catalog.model.user.UserRole;
import org.genesys.catalog.service.filters.UserFilter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
/**
* The Interface UserService.
*/
public interface UserService extends BasicUserService<UserRole, User> {
/**
* Get user by uuid
*
* @param uuid user's uuid
* @return the user
*/
// FIXME add to app-blocks
User getUser(UUID uuid);
/**
* Autocomplete user.
*
......@@ -35,4 +49,23 @@ public interface UserService extends BasicUserService<UserRole, User> {
*/
List<User> autocompleteUser(String term, int limit);
/**
* Gets the user.
*
* @param uuid the uuid
* @param version the version
* @return the user
*/
User getUser(UUID uuid, int version);
/**
* List users matching the filter.
*
* @param filter filter data
* @param page page
* @return list of User
*/
@PreAuthorize("hasRole('ADMINISTRATOR')")
Page<User> listUsers(UserFilter filter, Pageable page);
}
/*
* 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.genesys.catalog.service.filters;
import static org.genesys.catalog.model.user.QUser.user;
import java.util.Set;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.DateTimeExpression;
import org.apache.commons.collections4.CollectionUtils;
import org.genesys.blocks.model.filters.AuditedVersionedModelFilter;
import org.genesys.blocks.model.filters.StringFilter;
import org.genesys.blocks.security.model.BasicUser;
import org.genesys.catalog.model.user.UserRole;
/**
* The Class UserFilter.
*
* @author Maxym Borodenko
*/
public class UserFilter extends AuditedVersionedModelFilter {
/**
* The full name.
*/
public StringFilter fullName;
/**
* The email.
*/
public StringFilter email;
/**
* The user role.
*/
public Set<UserRole> role;
/**
* The account type.
*/
public Set<BasicUser.AccountType> accountType;
/**
* The locked.
*/
public Boolean locked;
/**
* The expired.
*/
public Boolean expired;
/**
* The passwordExpired.
*/
public Boolean passwordExpired;
/**
* Builds the query.
*
* @return the predicate
*/
public Predicate buildQuery() {
final BooleanBuilder and = new BooleanBuilder();
super.buildQuery(user._super._super._super, and);
if (fullName != null) {
and.and(fullName.buildQuery(user.fullName));
}
if (email != null) {
and.and(email.buildQuery(user.email));
}
if ((accountType != null) && !accountType.isEmpty()) {
and.and(user.accountType.in(accountType));
}
if (CollectionUtils.isNotEmpty(role)) {
and.and(user.roles.any().in(role));
}
if (locked != null && locked) {
and.and(user.lockedUntil.gt(DateTimeExpression.currentDate()));
}
if (expired != null && expired) {
and.and(user.accountExpires.lt(DateTimeExpression.currentDate()));
}
if (passwordExpired != null && passwordExpired) {
and.and(user.passwordExpires.lt(DateTimeExpression.currentDate()));
}
return and;
}
}
......@@ -15,6 +15,8 @@
*/
package org.genesys.catalog.service.impl;
import static org.genesys.catalog.model.dataset.QDataset.dataset;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
......@@ -240,8 +242,8 @@ public class DatasetServiceImpl implements DatasetService {
*/
@Override
public Page<Dataset> listDatasetsForCurrentUser(final DatasetFilter filter, final Pageable page) {
filter.id = new HashSet<>(utils.listObjectIdentityIdsForCurrentUser(Dataset.class, BasePermission.WRITE));
return datasetRepository.findAll(filter.buildQuery(), page);
HashSet<Long> ids = new HashSet<>(utils.listObjectIdentityIdsForCurrentUser(Dataset.class, BasePermission.WRITE));
return datasetRepository.findAll(dataset.id.in(ids).and(filter.buildQuery()), page);
}
/**
......
......@@ -15,6 +15,8 @@
*/
package org.genesys.catalog.service.impl;
import static org.genesys.catalog.model.traits.QDescriptorList.descriptorList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
......@@ -267,8 +269,8 @@ public class DescriptorListServiceImpl implements DescriptorListService {
@Override
public Page<DescriptorList> listDescriptorListsForCurrentUser(DescriptorListFilter filter, Pageable page) {
filter.id = new HashSet<>(utils.listObjectIdentityIdsForCurrentUser(DescriptorList.class, BasePermission.WRITE));
return descriptorListRepository.findAll(filter.buildQuery(), page);
HashSet<Long> ids = new HashSet<>(utils.listObjectIdentityIdsForCurrentUser(DescriptorList.class, BasePermission.WRITE));
return descriptorListRepository.findAll(descriptorList.id.in(ids).and(filter.buildQuery()), page);
}
// TODO implement logic
......
......@@ -15,6 +15,8 @@
*/
package org.genesys.catalog.service.impl;
import static org.genesys.catalog.model.traits.QDescriptor.descriptor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
......@@ -180,8 +182,8 @@ public class DescriptorServiceImpl implements DescriptorService {
*/
@Override
public Page<Descriptor> listDescriptorsForCurrentUser(final DescriptorFilter filter, final Pageable page) {
filter.id = new HashSet<>(utils.listObjectIdentityIdsForCurrentUser(Descriptor.class, BasePermission.WRITE));
return descriptorRepository.findAll(filter.buildQuery(), page);
HashSet<Long> ids = new HashSet<>(utils.listObjectIdentityIdsForCurrentUser(Descriptor.class, BasePermission.WRITE));
return descriptorRepository.findAll(descriptor.id.in(ids).and(filter.buildQuery()), page);
}
/**
......
......@@ -15,6 +15,8 @@
*/
package org.genesys.catalog.service.impl;
import static org.genesys.common.model.QPartner.partner;
import java.util.HashSet;
import java.util.UUID;
......@@ -144,8 +146,8 @@ public class PartnerServiceImpl implements PartnerService, InitializingBean {
*/
@Override
public Page<Partner> listPartnersForCurrentUser(final PartnerFilter filter, final Pageable page) {
filter.id = new HashSet<>(utils.listObjectIdentityIdsForCurrentUser(Partner.class, BasePermission.WRITE));
return partnerRepository.findAll(filter.buildQuery(), page);
HashSet<Long> ids = new HashSet<>(utils.listObjectIdentityIdsForCurrentUser(Partner.class, BasePermission.WRITE));
return partnerRepository.findAll(partner.id.in(ids).and(filter.buildQuery()), page);
}
/**
......
......@@ -17,6 +17,7 @@ package org.genesys.catalog.service.impl;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.genesys.blocks.security.NotUniqueUserException;
......@@ -28,10 +29,14 @@ import org.genesys.catalog.model.user.User;
import org.genesys.catalog.model.user.UserRole;
import org.genesys.catalog.persistence.user.UserRepository;
import org.genesys.catalog.service.UserService;
import org.genesys.catalog.service.filters.UserFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
......@@ -85,6 +90,11 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
return deepLoad(userRepository.findByEmail(email));
}
@Override
public User getUser(UUID uuid) {
return deepLoad(userRepository.findByUuid(uuid.toString()));
}
@Override
@Transactional
public User createUser(String email, String fullName, String password, AccountType accountType) throws NotUniqueUserException, PasswordPolicyException, UserException {
......@@ -104,4 +114,17 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
return Collections.emptyList();
return userRepository.autocomplete(email, new PageRequest(0, Integer.min(100, limit), new Sort("email")));
}
@Override
public User getUser(final UUID uuid, final int version) {
final User user = userRepository.getByUuidAndVersion(uuid.toString(), version);
if (user == null)
throw new ConcurrencyFailureException("Record with that version doesn't exist");
return deepLoad(user);
}
@Override
public Page<User> listUsers(final UserFilter filter, final Pageable page) {
return userRepository.findAll(filter.buildQuery(), page);
}
}
/*
* 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.genesys.catalog.util;
import java.util.Random;
/**
* @author Maxym Borodenko
*/
public class RandomPasswordUtil {
private static final String symbols = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$&@?~!%#";
/**
* This method generates a random password that consists of at least one special character, one number,
* one lowercase letter and one uppercase letter
*
* @param random
* @param length the password length that should be generated
* @return string with generated password
* @throws IllegalArgumentException if the 'length' parameter is lower than 4 characters
*/
public static String generatePassword(final Random random, final int length) {
if (length < 4) {
throw new IllegalArgumentException("Password must be at least 4 characters");
}
while (true) {
char[] password = new char[length];
boolean hasUpper = false, hasLower = false, hasDigit = false, hasSpecial = false;
for (int i = 0; i < password.length; i++) {
char ch = symbols.charAt(random.nextInt(symbols.length()));
if (Character.isUpperCase(ch))
hasUpper = true;
else if (Character.isLowerCase(ch))
hasLower = true;
else if (Character.isDigit(ch))
hasDigit = true;
else
hasSpecial = true;
password[i] = ch;
}
if (hasUpper && hasLower && hasDigit && hasSpecial) {
return new String(password);
}
}
}
}
......@@ -45,6 +45,8 @@ public class Utils {
*/
public List<Long> listObjectIdentityIdsForCurrentUser(final Class<? extends AclAwareModel> clazz, final Permission permission) {
final AclSid userSid = SecurityContextUtil.getCurrentUser();
// System.err.println("Current user " + userSid + " for " + permission + " for "
// + clazz.toString());
return aclService.listObjectIdentityIdsForSid(clazz, userSid, permission);
}
}
/*
* 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.genesys.catalog.server.controller.api.v0.admin;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import org.genesys.blocks.security.UserException;
import org.genesys.catalog.model.user.User;
import org.genesys.catalog.model.user.UserRole;
import org.genesys.catalog.service.UserService;
import org.genesys.catalog.service.filters.UserFilter;
import org.genesys.catalog.util.RandomPasswordUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* The Class UserAdminController.
*
* @author Maxym Borodenko
* @author Matija Obreza
*/
@RestController
@RequestMapping(UserAdminController.API_BASE)
@PreAuthorize("hasRole('ADMINISTRATOR')")
public class UserAdminController {
/** The Constant API_BASE. */
protected static final String API_BASE = "/api/v0/admin/user";
private static final Logger LOG = LoggerFactory.getLogger(UserAdminController.class);
@Autowired
private UserService userService;
/**