-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Currently only implemented for S3 storage type.
- Loading branch information
1 parent
00ad614
commit 1e60613
Showing
12 changed files
with
503 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/main/java/org/radarbase/appserver/config/S3StorageProperties.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/* | ||
* | ||
* * Copyright 2024 The Hyve | ||
* * | ||
* * 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.radarbase.appserver.config; | ||
|
||
import lombok.Data; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
@Data | ||
@ConfigurationProperties("storage.s3") | ||
public class S3StorageProperties { | ||
private String url; | ||
private String accessKey; | ||
private String secretKey; | ||
private String bucketName; | ||
private String subPath; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
src/main/java/org/radarbase/appserver/controller/UploadController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* | ||
* * Copyright 2024 The Hyve | ||
* * | ||
* * 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.radarbase.appserver.controller; | ||
|
||
import lombok.extern.slf4j.Slf4j; | ||
import org.radarbase.appserver.config.AuthConfig.AuthEntities; | ||
import org.radarbase.appserver.config.AuthConfig.AuthPermissions; | ||
import org.radarbase.appserver.service.storage.StorageService; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.CrossOrigin; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.multipart.MultipartFile; | ||
import radar.spring.auth.common.Authorized; | ||
import radar.spring.auth.common.PermissionOn; | ||
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
|
||
/** | ||
* Resource Endpoint for uploading assets to a data store. | ||
* | ||
* @author Pim van Nierop | ||
*/ | ||
@CrossOrigin | ||
@RestController | ||
@ConditionalOnProperty(value = "file-upload.enabled", havingValue = "true") | ||
@Slf4j | ||
public class UploadController { | ||
|
||
@Autowired | ||
private StorageService storageService; | ||
|
||
@Authorized( | ||
permission = AuthPermissions.UPDATE, | ||
entity = AuthEntities.SUBJECT, | ||
permissionOn = PermissionOn.SUBJECT | ||
) | ||
@PostMapping( | ||
"/" + PathsUtil.PROJECT_PATH + "/" + PathsUtil.PROJECT_ID_CONSTANT + | ||
"/" + PathsUtil.USER_PATH + "/" + PathsUtil.SUBJECT_ID_CONSTANT + | ||
"/" + PathsUtil.TOPIC_PATH + "/" + PathsUtil.TOPIC_ID_CONSTANT + | ||
"/upload") | ||
public ResponseEntity<?> subjectFileUpload( | ||
@RequestParam("file") MultipartFile file, | ||
@PathVariable String projectId, | ||
@PathVariable String subjectId, | ||
@PathVariable String topicId) throws URISyntaxException { | ||
|
||
log.info("Storing file for project: {}, subject: {}, topic: {}", projectId, subjectId, topicId); | ||
|
||
String filePath = storageService.store(file, projectId, subjectId, topicId); | ||
return ResponseEntity.created(new URI(filePath)).build(); | ||
} | ||
|
||
} |
67 changes: 67 additions & 0 deletions
67
src/main/java/org/radarbase/appserver/service/storage/MinioClientInitializer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
* | ||
* * Copyright 2024 The Hyve | ||
* * | ||
* * 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.radarbase.appserver.service.storage; | ||
|
||
import io.minio.BucketExistsArgs; | ||
import io.minio.MinioClient; | ||
import org.radarbase.appserver.config.S3StorageProperties; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | ||
import org.springframework.stereotype.Component; | ||
|
||
import javax.annotation.PostConstruct; | ||
|
||
@Component | ||
@ConditionalOnExpression("${file-upload.enabled:false} and 's3' == '${storage.type:}'") | ||
public class MinioClientInitializer { | ||
|
||
private MinioClient minioClient; | ||
private String bucketName; | ||
|
||
@Autowired | ||
private S3StorageProperties s3StorageProperties; | ||
|
||
@PostConstruct | ||
public void init() { | ||
try { | ||
minioClient = | ||
MinioClient.builder() | ||
.endpoint(s3StorageProperties.getUrl()) | ||
.credentials(s3StorageProperties.getAccessKey(), s3StorageProperties.getSecretKey()) | ||
.build(); | ||
bucketName = s3StorageProperties.getBucketName(); | ||
boolean found = | ||
minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); | ||
if (!found) { | ||
throw new RuntimeException(String.format("S3 bucket '%s' does not exist", bucketName)); | ||
} | ||
} catch (Exception e) { | ||
throw new RuntimeException("Could not connect to S3", e); | ||
} | ||
} | ||
|
||
public MinioClient getClient() { | ||
return minioClient; | ||
} | ||
|
||
public String getBucketName() { | ||
return bucketName; | ||
} | ||
|
||
} |
41 changes: 41 additions & 0 deletions
41
src/main/java/org/radarbase/appserver/service/storage/RandomUuidFilenameStorageService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* | ||
* * Copyright 2024 The Hyve | ||
* * | ||
* * 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.radarbase.appserver.service.storage; | ||
|
||
import java.util.UUID; | ||
|
||
public abstract class RandomUuidFilenameStorageService { | ||
|
||
// Storing files under their original filename is a security risk, as it can be used to | ||
// overwrite existing files. This method generates a random filename to mitigate this risk. | ||
// See https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload | ||
String generateRandomFilename(String originalFilename) { | ||
return UUID.randomUUID() + getFileExtension(originalFilename); | ||
} | ||
|
||
private String getFileExtension(String originalFilename) { | ||
int lastDot = originalFilename.lastIndexOf('.'); | ||
if (lastDot < 0) { | ||
return ""; | ||
} else { | ||
return originalFilename.substring(lastDot); | ||
} | ||
} | ||
|
||
} |
73 changes: 73 additions & 0 deletions
73
src/main/java/org/radarbase/appserver/service/storage/S3StorageService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
* | ||
* * Copyright 2024 The Hyve | ||
* * | ||
* * 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.radarbase.appserver.service.storage; | ||
|
||
import io.minio.PutObjectArgs; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.radarbase.appserver.config.S3StorageProperties; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.util.Assert; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import javax.annotation.PostConstruct; | ||
|
||
|
||
@Slf4j | ||
@Service | ||
@ConditionalOnExpression("${file-upload.enabled:false} and 's3' == '${storage.type:}'") | ||
public class S3StorageService extends RandomUuidFilenameStorageService implements StorageService { | ||
|
||
@Autowired | ||
private S3StorageProperties s3StorageProperties; | ||
|
||
@Autowired | ||
private MinioClientInitializer bucketClient; | ||
private String subPath = ""; | ||
|
||
@PostConstruct | ||
public void init() { | ||
if (s3StorageProperties.getSubPath() != null) { | ||
subPath = s3StorageProperties.getSubPath().replaceAll("^/|/$", ""); | ||
if (!subPath.isEmpty()) { | ||
subPath = subPath + "/"; | ||
} | ||
} | ||
} | ||
|
||
public String store(MultipartFile file, String projectId, String subjectId, String topicId) { | ||
Assert.notNull(file, "File must not be null"); | ||
Assert.notEmpty(new String[]{projectId, subjectId, topicId}, "Project, subject and topic IDs must not be empty"); | ||
String filePath = String.format("%s%s/%s/%s/%s", subPath, projectId, subjectId, topicId, generateRandomFilename(file.getOriginalFilename())); | ||
log.debug("Storing file at path: {}", filePath); | ||
try { | ||
bucketClient.getClient().putObject(PutObjectArgs | ||
.builder() | ||
.bucket(bucketClient.getBucketName()) | ||
.object(filePath) | ||
.stream(file.getInputStream(), file.getSize(), -1) | ||
.build()); | ||
} catch (Exception e) { | ||
throw new RuntimeException("Could not store file", e); | ||
} | ||
return filePath; | ||
} | ||
|
||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/org/radarbase/appserver/service/storage/StorageService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package org.radarbase.appserver.service.storage; | ||
|
||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
public interface StorageService { | ||
String store(MultipartFile file, String projectId, String subjectId, String topicId); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.