From 3cf2425544fe917d5eaeb22f9a8e9d201d124936 Mon Sep 17 00:00:00 2001
From: Johannes Buechele <johannes@bujo.at>
Date: Wed, 4 Oct 2023 16:06:50 +0200
Subject: [PATCH] added default quota to bucket and api to set quotas

---
 .../api/GlobalExceptionHandler.java           | 22 ++++++++++
 .../faircommons/api_service/api/MinioApi.java | 26 +++++++++++
 .../api_service/api/PublicWorkApi.java        |  5 ---
 .../faircommons/api_service/api/WorkApi.java  |  5 ---
 .../api_service/api/WorkFileApi.java          |  7 +--
 .../src/main/resources/application.yml        |  9 +++-
 faircommons-services/common/pom.xml           |  6 ---
 .../common/minio/MinioConfiguration.java      | 19 +++++---
 .../common/minio/MinioProperties.java         | 19 ++++++++
 .../common/minio/MinioService.java            | 43 ++++++++++++++-----
 .../faircommons/common/minio/QuotaView.java   |  5 +++
 .../models/MinioConfigurationProperties.java  | 14 ------
 .../ResourceServerAutoConfigurer.java         |  3 +-
 .../src/main/resources/application.yml        |  3 +-
 .../src/main/resources/application.yml        |  3 +-
 faircommons-services/pom.xml                  | 13 ++++++
 .../src/main/resources/application.yml        |  3 +-
 17 files changed, 148 insertions(+), 57 deletions(-)
 create mode 100644 faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/GlobalExceptionHandler.java
 create mode 100644 faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/MinioApi.java
 create mode 100644 faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioProperties.java
 create mode 100644 faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/QuotaView.java
 delete mode 100644 faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/models/MinioConfigurationProperties.java

diff --git a/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/GlobalExceptionHandler.java b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/GlobalExceptionHandler.java
new file mode 100644
index 0000000..b28ae38
--- /dev/null
+++ b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/GlobalExceptionHandler.java
@@ -0,0 +1,22 @@
+package eu.fairkom.faircommons.api_service.api;
+
+import eu.fairkom.faircommons.common.minio.MinioException;
+import jakarta.persistence.EntityNotFoundException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+    @ExceptionHandler(EntityNotFoundException.class)
+    public ResponseEntity<?> handleEntityNotFound() {
+        return ResponseEntity.notFound().build();
+    }
+
+
+    @ExceptionHandler(MinioException.class)
+    public ResponseEntity<String> handleMinioException(MinioException e) {
+        return ResponseEntity.status(503)
+                .body("Minio Service temporarily unavailable, no upload possible: ");
+    }
+}
diff --git a/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/MinioApi.java b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/MinioApi.java
new file mode 100644
index 0000000..82837f1
--- /dev/null
+++ b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/MinioApi.java
@@ -0,0 +1,26 @@
+package eu.fairkom.faircommons.api_service.api;
+
+import eu.fairkom.faircommons.common.minio.MinioService;
+import eu.fairkom.faircommons.common.minio.QuotaView;
+import io.minio.admin.QuotaUnit;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/minio/")
+public class MinioApi {
+    private final MinioService minioService;
+
+    public MinioApi(MinioService minioService) {
+        this.minioService = minioService;
+    }
+
+    @PutMapping("/{bucketName}/quota")
+    public ResponseEntity<Void> setQuota(
+            @PathVariable String bucketName,
+            @RequestBody QuotaView quota) {
+
+        minioService.setQuota(bucketName, quota.size(), quota.unit());
+        return ResponseEntity.noContent().build();
+    }
+}
diff --git a/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/PublicWorkApi.java b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/PublicWorkApi.java
index 7940016..a95b4cb 100644
--- a/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/PublicWorkApi.java
+++ b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/PublicWorkApi.java
@@ -30,9 +30,4 @@ public class PublicWorkApi {
     public ResponseEntity<PublicWorkView> getWorkByGrid(@PathVariable String grid) {
         return ResponseEntity.of(workService.getPublicWorkByGrid(grid));
     }
-
-    @ExceptionHandler(EntityNotFoundException.class)
-    public ResponseEntity<?> handleEntityNotFound() {
-        return ResponseEntity.notFound().build();
-    }
 }
diff --git a/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/WorkApi.java b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/WorkApi.java
index de4ae6d..a99bad4 100644
--- a/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/WorkApi.java
+++ b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/WorkApi.java
@@ -41,9 +41,4 @@ public class WorkApi {
                         .build(workId))
                 .build();
     }
-
-    @ExceptionHandler(EntityNotFoundException.class)
-    public ResponseEntity<?> handleEntityNotFound() {
-        return ResponseEntity.notFound().build();
-    }
 }
diff --git a/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/WorkFileApi.java b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/WorkFileApi.java
index b2eced5..aecd968 100644
--- a/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/WorkFileApi.java
+++ b/faircommons-services/api-service/src/main/java/eu/fairkom/faircommons/api_service/api/WorkFileApi.java
@@ -25,7 +25,7 @@ public class WorkFileApi {
 
     @GetMapping("/upload/presigned-url")
     public ResponseEntity<FileUploadView> generatePresignedUploadUrl(@RequestParam String userId,
-                                                                     @RequestParam String filename) throws MinioException {
+                                                                     @RequestParam String filename) {
         return ResponseEntity.ok(workFileService.generateUploadUrl(userId, filename));
     }
 
@@ -50,9 +50,4 @@ public class WorkFileApi {
                 .contentType(MediaType.APPLICATION_OCTET_STREAM)
                 .body(fileResponse.inputStreamResource());
     }
-
-    @ExceptionHandler(EntityNotFoundException.class)
-    public ResponseEntity<?> handleEntityNotFound() {
-        return ResponseEntity.notFound().build();
-    }
 }
diff --git a/faircommons-services/api-service/src/main/resources/application.yml b/faircommons-services/api-service/src/main/resources/application.yml
index fbcb101..10bfd5a 100644
--- a/faircommons-services/api-service/src/main/resources/application.yml
+++ b/faircommons-services/api-service/src/main/resources/application.yml
@@ -34,13 +34,20 @@ server:
     context-path: /${spring.application.name}
 
 minio:
-  bucketPrefix: fairregister-
   region: eu-central-1
+  bucket:
+    prefix: fairregister-
+
 
 security:
   web:
     resource:
       defaultScope: fairregister.default
+      paths:
+        - path: /minio/**
+          scopes:
+            - fairregister.admin
+
     unsecured:
       paths:
         - /works/public/**
diff --git a/faircommons-services/common/pom.xml b/faircommons-services/common/pom.xml
index 19fc5c1..a3a6e2e 100644
--- a/faircommons-services/common/pom.xml
+++ b/faircommons-services/common/pom.xml
@@ -17,12 +17,6 @@
     </properties>
 
     <dependencies>
-        <dependency>
-            <groupId>io.minio</groupId>
-            <artifactId>minio</artifactId>
-            <version>${minio.version}</version>
-        </dependency>
-
         <dependency>
             <groupId>org.hibernate.orm</groupId>
             <artifactId>hibernate-jpamodelgen</artifactId>
diff --git a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioConfiguration.java b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioConfiguration.java
index 1841848..2d63b28 100644
--- a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioConfiguration.java
+++ b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioConfiguration.java
@@ -1,7 +1,7 @@
 package eu.fairkom.faircommons.common.minio;
 
-import eu.fairkom.faircommons.common.minio.models.MinioConfigurationProperties;
 import io.minio.MinioClient;
+import io.minio.admin.MinioAdminClient;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
@@ -9,12 +9,12 @@ import org.springframework.context.annotation.Configuration;
 
 @Configuration(proxyBeanMethods = false)
 @ConditionalOnClass(MinioClient.class)
-@EnableConfigurationProperties(MinioConfigurationProperties.class)
+@EnableConfigurationProperties(MinioProperties.class)
 public class MinioConfiguration {
-    private final MinioConfigurationProperties properties;
+    private final MinioProperties properties;
 
-    public MinioConfiguration(MinioConfigurationProperties minioConfigurationProperties) {
-        this.properties = minioConfigurationProperties;
+    public MinioConfiguration(MinioProperties minioProperties) {
+        this.properties = minioProperties;
     }
 
     @Bean
@@ -25,4 +25,13 @@ public class MinioConfiguration {
                 .credentials(properties.accessKey(), properties.secretKey())
                 .build();
     }
+
+    @Bean
+    public MinioAdminClient minioAdminClient() {
+        return MinioAdminClient.builder()
+                .endpoint(properties.url())
+                .region(properties.region())
+                .credentials(properties.accessKey(), properties.secretKey())
+                .build();
+    }
 }
diff --git a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioProperties.java b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioProperties.java
new file mode 100644
index 0000000..fbeecaa
--- /dev/null
+++ b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioProperties.java
@@ -0,0 +1,19 @@
+package eu.fairkom.faircommons.common.minio;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.bind.DefaultValue;
+
+@ConfigurationProperties("minio")
+public record MinioProperties(
+        String url,
+        String accessKey,
+        String secretKey,
+        boolean secure,
+        Bucket bucket,
+        @DefaultValue("eu-central-1") String region
+) {
+    record Bucket(
+            @DefaultValue("fairregister-") String prefix,
+            @DefaultValue("200") Long quota) {
+    }
+}
diff --git a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioService.java b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioService.java
index 2b88753..aa3d435 100644
--- a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioService.java
+++ b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/MinioService.java
@@ -1,8 +1,11 @@
 package eu.fairkom.faircommons.common.minio;
 
 import io.minio.*;
+import io.minio.admin.MinioAdminClient;
+import io.minio.admin.QuotaUnit;
 import io.minio.http.Method;
-import org.springframework.beans.factory.annotation.Value;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import java.io.InputStream;
@@ -13,13 +16,17 @@ import static eu.fairkom.faircommons.common.Constants.REGISTRATION_CERTIFICATE_N
 
 @Service
 public class MinioService {
+    private static final Logger logger = LoggerFactory.getLogger(MinioService.class);
     private final MinioClient minioClient;
-    @Value("${minio.bucketPrefix}")
-    private String prefix;
+    private final MinioAdminClient minioAdminClient;
 
+    private final MinioProperties properties;
 
-    public MinioService(MinioClient minioClient) {
+
+    public MinioService(MinioClient minioClient, MinioAdminClient minioAdminClient, MinioProperties properties) {
         this.minioClient = minioClient;
+        this.minioAdminClient = minioAdminClient;
+        this.properties = properties;
     }
 
     public String generatePresignedUploadUrl(String userId, String filename) throws MinioException {
@@ -35,12 +42,13 @@ public class MinioService {
     public InputStream downloadFile(String userId, String filename) throws MinioException {
         try {
             var downloadFileArgs = GetObjectArgs.builder()
-                    .bucket(prefix + userId)
+                    .bucket(properties.bucket().prefix() + userId)
                     .object(filename)
                     .build();
 
             return minioClient.getObject(downloadFileArgs);
         } catch (Exception e) {
+            logger.error("Error downloading file for user: {} and file: {}", userId, filename);
             throw new MinioException(e.getMessage(), e.getCause());
         }
     }
@@ -49,7 +57,7 @@ public class MinioService {
         try {
             var filename = filenamePrefix + "/" + REGISTRATION_CERTIFICATE_FOLDER + "/" + REGISTRATION_CERTIFICATE_NAME;
             var uploadFileArgs = PutObjectArgs.builder()
-                    .bucket(prefix + userId)
+                    .bucket(properties.bucket().prefix() + userId)
                     .object(filename)
                     .stream(inputStream, -1, 5242880)
                     .contentType("application/pdf")
@@ -57,6 +65,7 @@ public class MinioService {
 
             minioClient.putObject(uploadFileArgs);
         } catch (Exception e) {
+            logger.error("Error uploading registration certificate for user: {} with filename prefix: {}", userId, filenamePrefix);
             throw new MinioException(e.getMessage(), e.getCause());
         }
     }
@@ -69,12 +78,13 @@ public class MinioService {
     public void removeFile(String userId, String filename) throws MinioException {
         try {
             var removeFileArgs = RemoveObjectArgs.builder()
-                    .bucket(prefix + userId)
+                    .bucket(properties.bucket().prefix() + userId)
                     .object(filename)
                     .build();
 
             minioClient.removeObject(removeFileArgs);
         } catch (Exception e) {
+            logger.error("Error removing file for user: {} and file: {}", userId, filename, e);
             throw new MinioException(e.getMessage(), e.getCause());
         }
     }
@@ -83,9 +93,20 @@ public class MinioService {
         try {
             minioClient.makeBucket(
                     MakeBucketArgs.builder()
-                            .bucket(prefix + userId)
+                            .bucket(properties.bucket().prefix() + userId)
                             .build());
+            setQuota(properties.bucket().prefix() + userId, properties.bucket().quota(), QuotaUnit.MB);
+        } catch (Exception e) {
+            logger.error("Error creating bucket for user: {}", userId, e);
+            throw new MinioException(e.getMessage(), e.getCause());
+        }
+    }
+
+    public void setQuota(String bucketName, Long quota, QuotaUnit unit) {
+        try {
+            minioAdminClient.setBucketQuota(bucketName, quota, unit);
         } catch (Exception e) {
+            logger.error("Error setting quota for bucket: {}", bucketName, e);
             throw new MinioException(e.getMessage(), e.getCause());
         }
     }
@@ -93,8 +114,9 @@ public class MinioService {
     private boolean bucketExists(String userId) throws MinioException {
         try {
             return minioClient.bucketExists(BucketExistsArgs.builder()
-                    .bucket(prefix + userId).build());
+                    .bucket(properties.bucket().prefix() + userId).build());
         } catch (Exception e) {
+            logger.error("Error checking if bucket exists for user: {}", userId, e);
             throw new MinioException(e.getMessage(), e.getCause());
         }
     }
@@ -103,12 +125,13 @@ public class MinioService {
         try {
             return minioClient.getPresignedObjectUrl(
                     GetPresignedObjectUrlArgs.builder()
-                            .bucket(prefix + userId)
+                            .bucket(properties.bucket().prefix() + userId)
                             .object(filename)
                             .method(method)
                             .expiry(3, TimeUnit.HOURS)
                             .build());
         } catch (Exception e) {
+            logger.error("Error generating presigned object URL for user: {} and file: {}", userId, filename, e);
             throw new MinioException(e.getMessage(), e.getCause());
         }
     }
diff --git a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/QuotaView.java b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/QuotaView.java
new file mode 100644
index 0000000..91197ee
--- /dev/null
+++ b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/QuotaView.java
@@ -0,0 +1,5 @@
+package eu.fairkom.faircommons.common.minio;
+
+import io.minio.admin.QuotaUnit;
+
+public record QuotaView(long size, QuotaUnit unit) {}
\ No newline at end of file
diff --git a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/models/MinioConfigurationProperties.java b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/models/MinioConfigurationProperties.java
deleted file mode 100644
index 6372219..0000000
--- a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/minio/models/MinioConfigurationProperties.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package eu.fairkom.faircommons.common.minio.models;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@ConfigurationProperties("minio")
-public record MinioConfigurationProperties(
-        String url,
-        String accessKey,
-        String secretKey,
-        boolean secure,
-        String bucketPrefix,
-        String region
-) {
-}
diff --git a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/security/ResourceServerAutoConfigurer.java b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/security/ResourceServerAutoConfigurer.java
index 55dacbf..be94f47 100644
--- a/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/security/ResourceServerAutoConfigurer.java
+++ b/faircommons-services/common/src/main/java/eu/fairkom/faircommons/common/security/ResourceServerAutoConfigurer.java
@@ -80,8 +80,7 @@ public class ResourceServerAutoConfigurer {
             logger.info("Securing {} requests to {} with {}",
                     securedPath.methods(), securedPath.path(), securedPath.scopes());
             authorize.requestMatchers(matcher(securedPath))
-                    .hasAnyAuthority(asAuthorities(securedPath.scopes()))
-                    .anyRequest().authenticated();
+                    .hasAnyAuthority(asAuthorities(securedPath.scopes()));
         });
     }
 
diff --git a/faircommons-services/hashing-service/src/main/resources/application.yml b/faircommons-services/hashing-service/src/main/resources/application.yml
index 06d4f28..1992e49 100644
--- a/faircommons-services/hashing-service/src/main/resources/application.yml
+++ b/faircommons-services/hashing-service/src/main/resources/application.yml
@@ -16,8 +16,9 @@ server:
     context-path: /${spring.application.name}
 
 minio:
-  bucketPrefix: fairregister-
   region: eu-central-1
+  bucket:
+    prefix: fairregister-
 
 security:
   web:
diff --git a/faircommons-services/ipfs-service/src/main/resources/application.yml b/faircommons-services/ipfs-service/src/main/resources/application.yml
index 7c4df3e..14b86ac 100644
--- a/faircommons-services/ipfs-service/src/main/resources/application.yml
+++ b/faircommons-services/ipfs-service/src/main/resources/application.yml
@@ -17,8 +17,9 @@ server:
     context-path: /${spring.application.name}
 
 minio:
-  bucketPrefix: fairregister-
   region: eu-central-1
+  bucket:
+    prefix: fairregister-
 
 ipfs:
   service: daemon
diff --git a/faircommons-services/pom.xml b/faircommons-services/pom.xml
index 8bf7ff6..8ff5631 100644
--- a/faircommons-services/pom.xml
+++ b/faircommons-services/pom.xml
@@ -88,6 +88,19 @@
             <scope>runtime</scope>
         </dependency>
 
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>${minio.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio-admin</artifactId>
+            <version>${minio.version}</version>
+        </dependency>
+
+
         <!-- MapStruct -->
         <dependency>
             <groupId>org.mapstruct</groupId>
diff --git a/faircommons-services/post-registration-service/src/main/resources/application.yml b/faircommons-services/post-registration-service/src/main/resources/application.yml
index cc67ec5..f83f4df 100644
--- a/faircommons-services/post-registration-service/src/main/resources/application.yml
+++ b/faircommons-services/post-registration-service/src/main/resources/application.yml
@@ -21,8 +21,9 @@ server:
     context-path: /${spring.application.name}
 
 minio:
-  bucketPrefix: fairregister-
   region: eu-central-1
+  bucket:
+    prefix: fairregister-
 
 security:
   web:
-- 
GitLab