Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #157: Improve request validation #187

Merged
merged 3 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@
import com.wultra.security.userdatastore.client.model.request.AttachmentUpdateRequest;
import com.wultra.security.userdatastore.client.model.response.AttachmentCreateResponse;
import com.wultra.security.userdatastore.client.model.response.AttachmentResponse;
import com.wultra.security.userdatastore.model.validator.AttachmentRequestValidator;
import com.wultra.security.userdatastore.service.AttachmentService;
import io.getlime.core.rest.model.base.request.ObjectRequest;
import io.getlime.core.rest.model.base.response.ObjectResponse;
import io.getlime.core.rest.model.base.response.Response;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

Expand All @@ -43,14 +45,11 @@
@RestController
@Validated
@Slf4j
@AllArgsConstructor
class AttachmentController {

private final AttachmentService attachmentService;

@Autowired
AttachmentController(AttachmentService attachmentService) {
this.attachmentService = attachmentService;
}
private final AttachmentRequestValidator validator = new AttachmentRequestValidator();

/**
* Return attachments for the given user.
Expand Down Expand Up @@ -81,8 +80,9 @@ public ObjectResponse<AttachmentResponse> fetchAttachments(@NotBlank @Size(max =
description = "Create an attachment for the given user and document."
)
@PostMapping("/admin/attachments")
public ObjectResponse<AttachmentCreateResponse> createAttachment(@RequestBody final ObjectRequest<AttachmentCreateRequest> request) {
public ObjectResponse<AttachmentCreateResponse> createAttachment(@Valid @RequestBody final ObjectRequest<AttachmentCreateRequest> request) {
logger.info("Creating attachment for user ID: {}", request.getRequestObject().userId());
validator.validateRequest(request.getRequestObject());
final AttachmentCreateResponse response = attachmentService.createAttachment(request.getRequestObject());
return new ObjectResponse<>(response);
}
Expand All @@ -98,9 +98,11 @@ public ObjectResponse<AttachmentCreateResponse> createAttachment(@RequestBody fi
description = "Update an attachment."
)
@PutMapping("/admin/attachments/{attachmentId}")
public Response updateAttachment(@NotBlank @Size(max = 36) @PathVariable("attachmentId") String attachmentId, @RequestBody final ObjectRequest<AttachmentUpdateRequest> request) {
public Response updateAttachment(@NotBlank @Size(max = 36) @PathVariable("attachmentId") String attachmentId, @Valid @RequestBody final ObjectRequest<AttachmentUpdateRequest> request) {
logger.info("Updating attachment with ID: {}", attachmentId);
return attachmentService.updateAttachment(attachmentId, request.getRequestObject());
validator.validateRequest(request.getRequestObject());
attachmentService.updateAttachment(attachmentId, request.getRequestObject());
return new Response();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@
import com.wultra.security.userdatastore.client.model.request.DocumentUpdateRequest;
import com.wultra.security.userdatastore.client.model.response.DocumentCreateResponse;
import com.wultra.security.userdatastore.client.model.response.DocumentResponse;
import com.wultra.security.userdatastore.model.validator.DocumentRequestValidator;
import com.wultra.security.userdatastore.service.DocumentService;
import io.getlime.core.rest.model.base.request.ObjectRequest;
import io.getlime.core.rest.model.base.response.ObjectResponse;
import io.getlime.core.rest.model.base.response.Response;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

Expand All @@ -44,14 +45,11 @@
@RestController
@Validated
@Slf4j
@AllArgsConstructor
class DocumentController {

private final DocumentService documentService;

@Autowired
DocumentController(DocumentService documentService) {
this.documentService = documentService;
}
private final DocumentRequestValidator validator = new DocumentRequestValidator();

/**
* Return documents for the given user.
Expand Down Expand Up @@ -82,8 +80,9 @@ public ObjectResponse<DocumentResponse> fetchDocuments(@NotBlank @Size(max = 255
description = "Create a documents for the given user."
)
@PostMapping("/admin/documents")
public ObjectResponse<DocumentCreateResponse> createDocument(@RequestBody final ObjectRequest<DocumentCreateRequest> request) {
public ObjectResponse<DocumentCreateResponse> createDocument(@Valid @RequestBody final ObjectRequest<DocumentCreateRequest> request) {
logger.info("Creating document for user ID: {}", request.getRequestObject().userId());
validator.validateRequest(request.getRequestObject());
final DocumentCreateResponse response = documentService.createDocument(request.getRequestObject());
return new ObjectResponse<>(response);
}
Expand All @@ -100,8 +99,9 @@ public ObjectResponse<DocumentCreateResponse> createDocument(@RequestBody final
description = "Update a document for the given user."
)
@PutMapping("/admin/documents/{documentId}")
public Response updateDocument(@NotBlank @Size(max = 36) @PathVariable("documentId") String documentId, @RequestBody final ObjectRequest<DocumentUpdateRequest> request) {
public Response updateDocument(@NotBlank @Size(max = 36) @PathVariable("documentId") String documentId, @Valid @RequestBody final ObjectRequest<DocumentUpdateRequest> request) {
logger.info("Updating document for user ID: {}", request.getRequestObject().userId());
validator.validateRequest(request.getRequestObject());
documentService.updateDocument(documentId, request.getRequestObject());
return new Response();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@
import com.wultra.security.userdatastore.client.model.request.PhotoUpdateRequest;
import com.wultra.security.userdatastore.client.model.response.PhotoCreateResponse;
import com.wultra.security.userdatastore.client.model.response.PhotoResponse;
import com.wultra.security.userdatastore.model.validator.PhotoRequestValidator;
import com.wultra.security.userdatastore.service.PhotoService;
import io.getlime.core.rest.model.base.request.ObjectRequest;
import io.getlime.core.rest.model.base.response.ObjectResponse;
import io.getlime.core.rest.model.base.response.Response;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

Expand All @@ -43,14 +45,11 @@
@RestController
@Validated
@Slf4j
@AllArgsConstructor
class PhotoController {

private final PhotoService photoService;

@Autowired
PhotoController(PhotoService photoService) {
this.photoService = photoService;
}
private final PhotoRequestValidator validator = new PhotoRequestValidator();

/**
* Return photos for the given user and document.
Expand Down Expand Up @@ -81,8 +80,9 @@ public ObjectResponse<PhotoResponse> fetchPhotos(@NotBlank @Size(max = 255) @Req
description = "Create a photo for the given user and document."
)
@PostMapping("/admin/photos")
public ObjectResponse<PhotoCreateResponse> createPhoto(@RequestBody final ObjectRequest<PhotoCreateRequest> request) {
public ObjectResponse<PhotoCreateResponse> createPhoto(@Valid @RequestBody final ObjectRequest<PhotoCreateRequest> request) {
logger.info("Creating photo for user ID: {}", request.getRequestObject().userId());
validator.validateRequest(request.getRequestObject());
final PhotoCreateResponse response = photoService.createPhoto(request.getRequestObject());
return new ObjectResponse<>(response);
}
Expand All @@ -98,9 +98,11 @@ public ObjectResponse<PhotoCreateResponse> createPhoto(@RequestBody final Object
description = "Update a photo."
)
@PutMapping("/admin/photos/{photoId}")
public Response updatePhoto(@NotBlank @Size(max = 36) @PathVariable("photoId") String photoId, @RequestBody final ObjectRequest<PhotoUpdateRequest> request) {
public Response updatePhoto(@NotBlank @Size(max = 36) @PathVariable("photoId") String photoId, @Valid @RequestBody final ObjectRequest<PhotoUpdateRequest> request) {
logger.info("Updating photo with ID: {}", photoId);
return photoService.updatePhoto(photoId, request.getRequestObject());
validator.validateRequest(request.getRequestObject());
photoService.updatePhoto(photoId, request.getRequestObject());
return new Response();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@

import com.wultra.security.userdatastore.model.error.EncryptionException;
import com.wultra.security.userdatastore.model.error.InvalidRequestException;
import com.wultra.security.userdatastore.model.error.RequestValidationException;
import com.wultra.security.userdatastore.model.error.ResourceAlreadyExistsException;
import com.wultra.security.userdatastore.model.error.ResourceNotFoundException;
import io.getlime.core.rest.model.base.response.ErrorResponse;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
Expand Down Expand Up @@ -70,14 +72,14 @@ public ErrorResponse handleEncryptionException(final EncryptionException e) {
}

/**
* Exception handler for {@link InvalidRequestException} or {@link ConstraintViolationException}.
* Exception handler for invalid request exceptions.
*
* @param e Exception.
* @return Response with error details.
*/
@ExceptionHandler({InvalidRequestException.class, ConstraintViolationException.class})
@ExceptionHandler({InvalidRequestException.class, ConstraintViolationException.class, MethodArgumentNotValidException.class, RequestValidationException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleInvalidRequestException(final RuntimeException e) {
public ErrorResponse handleInvalidRequestException(final Exception e) {
logger.warn("Error occurred when processing request object.", e);
return new ErrorResponse("INVALID_REQUEST", e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* User Data Store
* Copyright (C) 2024 Wultra s.r.o.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.wultra.security.userdatastore.model.error;

/**
* Exception to be thrown when request validation fails.
*
* @author Roman Strobl, [email protected]
*/
public class RequestValidationException extends RuntimeException {

/**
* No-arg constructor.
*/
public RequestValidationException() {
}

/**
* Constructor with message.
*
* @param message Error message.
*/
public RequestValidationException(String message) {
super(message);
}

/**
* Constructs a new exception with the specified cause.
*
* @param cause Error cause.
*/
public RequestValidationException(Throwable cause) {
super(cause);
}

/**
* Constructor with message and specified cause.
*
* @param message Error message.
* @param cause Error cause.
*/
public RequestValidationException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* User Data Store
* Copyright (C) 2024 Wultra s.r.o.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.wultra.security.userdatastore.model.validator;

import com.wultra.security.userdatastore.client.model.request.*;
import com.wultra.security.userdatastore.model.error.RequestValidationException;
import org.springframework.util.StringUtils;

import java.util.Base64;

/**
* Validator for attachment requests.
*
* @author Roman Strobl, [email protected]
*/
public class AttachmentRequestValidator {

public void validateRequest(final AttachmentCreateRequest request) {
romanstrobl marked this conversation as resolved.
Show resolved Hide resolved
validateDataType(request.attachmentType(), request.attachmentData());
}

public void validateRequest(final AttachmentUpdateRequest request) {
validateDataType(request.attachmentType(), request.attachmentData());
}

public void validateRequest(final EmbeddedAttachmentCreateRequest request) {
validateDataType(request.attachmentType(), request.attachmentData());
}

private void validateDataType(final String attachmentType, final String attachmentData) {
switch (attachmentType) {
case "image_base64":
case "binary_base64":
try {
Base64.getDecoder().decode(attachmentData);
} catch (IllegalArgumentException e) {
throw new RequestValidationException(e.getMessage(), e);
}
break;
case "text":
if (!StringUtils.hasText(attachmentData)) {
throw new RequestValidationException("Missing attachment data for type 'text'.");
}
break;
}
}
}
Loading
Loading