Skip to content

Commit

Permalink
EPMRPP-97060 implemented upload user avatar (#2142)
Browse files Browse the repository at this point in the history
  • Loading branch information
grabsefx authored Jan 15, 2025
1 parent 9dc3f4f commit 811ee7b
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2025 EPAM Systems
*
* 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.epam.ta.reportportal.auth.permissions;

import com.epam.reportportal.rules.commons.validation.BusinessRule;
import com.epam.reportportal.rules.exception.ErrorType;
import com.epam.reportportal.rules.exception.ReportPortalException;
import com.epam.ta.reportportal.commons.ReportPortalUser;
import com.epam.ta.reportportal.dao.UserRepository;
import java.util.Objects;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.stereotype.Component;

/**
* Check if provided user id belongs to authenticated user.
*
* @author Andrei Varabyeu
*/
@Component("allowedToUserItselfPermission")
@LookupPermission({"allowedToUserItself"})
public class AllowedToUserItself implements Permission {

private final UserRepository userRepository;

public AllowedToUserItself(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public boolean isAllowed(Authentication authentication, Object id) {
OAuth2Authentication oauth = (OAuth2Authentication) authentication;

ReportPortalUser rpUser = (ReportPortalUser) oauth.getUserAuthentication().getPrincipal();
BusinessRule.expect(rpUser, Objects::nonNull).verify(ErrorType.ACCESS_DENIED);

Long userIdParameter = Long.parseLong(String.valueOf(id));
var user = userRepository
.findById(userIdParameter)
.orElseThrow(() -> new ReportPortalException(ErrorType.USER_NOT_FOUND, userIdParameter));

return rpUser.getUserId().equals(user.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,7 @@ private Permissions() {

public static final String INVITATION_ALLOWED = IS_ADMIN + "||"
+ "hasPermission(#invitationRequest, 'invitationAllowed')";

public static final String ALLOWED_TO_USER_ITSELF = IS_ADMIN + "||"
+ "hasPermission(#userId, 'allowedToUserItself')";
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,23 @@ public interface EditUserHandler {
OperationCompletionRS editUser(String username, EditUserRQ editUserRQ, ReportPortalUser editor);

/**
* Upload photo
* Upload photo.
*
* @param username Name of user
* @param file New photo
* @return Completion result
*/
OperationCompletionRS uploadPhoto(String username, MultipartFile file);

/**
* Upload photo.
*
* @param userId id of user
* @param file New photo
* @return Completion result
*/
OperationCompletionRS uploadPhoto(Long userId, MultipartFile file);

/**
* Delete user's photo
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ public OperationCompletionRS uploadPhoto(String username, MultipartFile file) {
return new OperationCompletionRS("Profile photo has been uploaded successfully");
}

@Override
public OperationCompletionRS uploadPhoto(Long userId, MultipartFile file) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ReportPortalException(ErrorType.USER_NOT_FOUND, userId));
validatePhoto(file);
userBinaryDataService.saveUserPhoto(user, file);
return new OperationCompletionRS("Profile photo has been uploaded successfully");
}

@Override
public OperationCompletionRS deletePhoto(String login) {
User user = userRepository.findByLogin(login)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,13 @@

package com.epam.ta.reportportal.ws.controller;

import static com.epam.ta.reportportal.auth.permissions.Permissions.IS_ADMIN;
import static com.epam.ta.reportportal.auth.permissions.Permissions.ALLOWED_TO_USER_ITSELF;

import com.epam.reportportal.api.UserApi;
import com.epam.reportportal.api.model.AccountType;
import com.epam.reportportal.api.model.InstanceRole;
import com.epam.reportportal.api.model.InstanceUser;
import com.epam.reportportal.api.model.InstanceUserPage;
import com.epam.reportportal.api.model.Order;
import com.epam.ta.reportportal.commons.querygen.Filter;
import com.epam.ta.reportportal.core.file.GetFileHandler;
import com.epam.ta.reportportal.core.user.EditUserHandler;
import com.epam.ta.reportportal.core.user.GetUserHandler;
import com.epam.ta.reportportal.util.ControllerUtils;
import com.epam.ta.reportportal.util.DefaultUserFilter;
import java.util.UUID;
import io.swagger.v3.oas.annotations.Parameter;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
Expand All @@ -39,18 +32,24 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
public class UserController extends BaseController implements UserApi {

private final GetFileHandler getFileHandler;
private final EditUserHandler editUserHandler;
private final GetUserHandler getUserHandler;

private final HttpServletRequest httpServletRequest;

public UserController(GetFileHandler getFileHandler, GetUserHandler getUserHandler, HttpServletRequest httpServletRequest) {
public UserController(GetFileHandler getFileHandler, EditUserHandler editUserHandler,
GetUserHandler getUserHandler,
HttpServletRequest httpServletRequest) {
this.getFileHandler = getFileHandler;
this.editUserHandler = editUserHandler;
this.getUserHandler = getUserHandler;
this.httpServletRequest = httpServletRequest;
}
Expand Down Expand Up @@ -94,4 +93,15 @@ public ResponseEntity<Resource> getUsersUserIdAvatar(Long userId, Boolean thumbn
"attachment; filename=\"" + binaryData.getFileName() + "\"")
.body(resource);
}

@Override
@Transactional
@PreAuthorize(ALLOWED_TO_USER_ITSELF)
public ResponseEntity<Void> postUsersUserIdAvatar(Long userId,
@Parameter(name = "file", description = "")
@RequestPart(value = "file", required = false) MultipartFile file) {

editUserHandler.uploadPhoto(userId, file);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,20 @@ class EditUserHandlerImplTest {

@Test
void uploadNotExistUserPhoto() {
when(userRepository.findByLogin("not_exists")).thenReturn(Optional.empty());
when(userRepository.findById(4004L)).thenReturn(Optional.empty());

final ReportPortalException exception = assertThrows(ReportPortalException.class,
() -> handler.uploadPhoto("not_exists", new MockMultipartFile("photo", new byte[100]))
() -> handler.uploadPhoto(4004L, new MockMultipartFile("photo", new byte[100]))
);
assertEquals("User 'not_exists' not found.", exception.getMessage());
assertEquals("User '4004' not found.", exception.getMessage());
}

@Test
void uploadOversizePhoto() {
when(userRepository.findByLogin("test")).thenReturn(Optional.of(new User()));
when(userRepository.findById(1L)).thenReturn(Optional.of(new User()));

final ReportPortalException exception = assertThrows(ReportPortalException.class,
() -> handler.uploadPhoto("test",
() -> handler.uploadPhoto(1L,
new MockMultipartFile("photo", new byte[1024 * 1024 + 10])
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ class FileStorageControllerTest extends BaseMvcTest {
@Test
void userPhoto() throws Exception {
final MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart(
"/v1/data/photo")
"/users/2/avatar")
.file(new MockMultipartFile("file", "file", "image/png",
new ClassPathResource("image/image.png").getInputStream()))
.contentType(MediaType.MULTIPART_FORM_DATA);

mockMvc.perform(requestBuilder.with(token(oAuthHelper.getDefaultToken())))
.andExpect(status().isOk());
.andExpect(status().isNoContent());

mockMvc.perform(get("/v1/data/photo").with(token(oAuthHelper.getDefaultToken())))
mockMvc.perform(get("/users/2/avatar").with(token(oAuthHelper.getDefaultToken())))
.andExpect(status().isOk());

mockMvc.perform(get("/v1/data/default_personal/userphoto?login=default").with(
Expand All @@ -84,7 +84,7 @@ public void testUserPhotoAccessDeniedForCustomer() throws Exception {
@Test
void uploadLargeUserPhoto() throws Exception {
final MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart(
"/v1/data/photo")
"/users/2/avatar")
.file(new MockMultipartFile("file",
new ClassPathResource("image/large_image.png").getInputStream()))
.contentType(MediaType.MULTIPART_FORM_DATA);
Expand Down Expand Up @@ -118,7 +118,7 @@ void cleanAttachmentsByCvs() throws Exception {
@Test
void uploadNotImage() throws Exception {
final MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart(
"/v1/data/photo")
"/users/2/avatar")
.file(new MockMultipartFile("file", "text.txt", "text/plain",
"test".getBytes(StandardCharsets.UTF_8)))
.contentType(MediaType.MULTIPART_FORM_DATA);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ void createdUserByIdentityProvider() throws Exception {
assertEquals(normalizeId(rq.getLogin()), createUserRS.getLogin());
var user = userRepository.findById(createUserRS.getId());
assertTrue(user.isPresent());
assertEquals(user.get().getUserType(), UserType.SCIM);
assertEquals(UserType.SCIM, user.get().getUserType());
assertNull(user.get().getPassword());

var projectOptional = projectRepository.findByName("default_personal");
Expand Down Expand Up @@ -486,13 +486,13 @@ void exportUsers() throws Exception {
@Test
void userPhoto() throws Exception {
final MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart(
"/v1/data/photo")
"/users/2/avatar")
.file(new MockMultipartFile("file", "file", "image/png",
new ClassPathResource("image/image.png").getInputStream()))
.contentType(MediaType.MULTIPART_FORM_DATA);

mockMvc.perform(requestBuilder.with(token(oAuthHelper.getDefaultToken())))
.andExpect(status().isOk());
.andExpect(status().isNoContent());

mockMvc.perform(get("/users/2/avatar?thumbnail=false")
.with(token(oAuthHelper.getDefaultToken())))
Expand Down

0 comments on commit 811ee7b

Please sign in to comment.