From a0239ecda6ed8f40b814d8469e47024caddf1bf0 Mon Sep 17 00:00:00 2001 From: MK13 Date: Sat, 13 Aug 2022 18:40:13 +0200 Subject: [PATCH] Basic implementation of files API that allows other apps filesystem access --- dashboard/pom.xml | 4 ++ .../controller/DashboardWidgetController.java | 1 + files-api/pom.xml | 27 ++++++++ .../de/nbscloud/files/api/FilesService.java | 33 ++++++++++ files/pom.xml | 4 ++ .../de/nbscloud/files/AppLocationTracker.java | 54 ++++++++++++++++ .../de/nbscloud/files/FileSystemService.java | 22 +++++-- .../de/nbscloud/files/FilesServiceImpl.java | 61 +++++++++++++++++++ .../de/nbscloud/files/LocationTracker.java | 34 ++++------- .../de/nbscloud/files/config/FilesConfig.java | 9 +++ .../files/controller/FilesController.java | 9 ++- .../config/files-application.properties | 5 +- pom.xml | 8 ++- 13 files changed, 237 insertions(+), 34 deletions(-) create mode 100644 files-api/pom.xml create mode 100644 files-api/src/main/java/de/nbscloud/files/api/FilesService.java create mode 100644 files/src/main/java/de/nbscloud/files/AppLocationTracker.java create mode 100644 files/src/main/java/de/nbscloud/files/FilesServiceImpl.java diff --git a/dashboard/pom.xml b/dashboard/pom.xml index 0084017..0fac68e 100644 --- a/dashboard/pom.xml +++ b/dashboard/pom.xml @@ -31,6 +31,10 @@ spring-boot-starter-web provided + + de.77zzcx7.nbs-cloud + files-api + \ No newline at end of file diff --git a/dashboard/src/main/java/de/nbscloud/dashboard/controller/DashboardWidgetController.java b/dashboard/src/main/java/de/nbscloud/dashboard/controller/DashboardWidgetController.java index 7e92181..16e2e5b 100644 --- a/dashboard/src/main/java/de/nbscloud/dashboard/controller/DashboardWidgetController.java +++ b/dashboard/src/main/java/de/nbscloud/dashboard/controller/DashboardWidgetController.java @@ -26,6 +26,7 @@ public class DashboardWidgetController { @GetMapping("dashboard/widgets/machineMetrics") public String getDiskUsage(HttpServletRequest request, HttpServletResponse response, Model model) { + model.addAttribute("interfaces", metricService.getInterfaces()); model.addAttribute("updates", metricService.getUpdates()); diff --git a/files-api/pom.xml b/files-api/pom.xml new file mode 100644 index 0000000..858b058 --- /dev/null +++ b/files-api/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + + de.77zzcx7.nbs-cloud + nbs-cloud-aggregator + 18-SNAPSHOT + + + de.77zzcx7.nbs-cloud + files-api + jar + + + + org.springframework.boot + spring-boot-starter-web + provided + + + de.77zzcx7.nbs-cloud + web-container-registry + + + + diff --git a/files-api/src/main/java/de/nbscloud/files/api/FilesService.java b/files-api/src/main/java/de/nbscloud/files/api/FilesService.java new file mode 100644 index 0000000..403498f --- /dev/null +++ b/files-api/src/main/java/de/nbscloud/files/api/FilesService.java @@ -0,0 +1,33 @@ +package de.nbscloud.files.api; + +import de.nbscloud.webcontainer.registry.App; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +public interface FilesService { + record ContentContainer(boolean directory, Path path, String name, long size, + LocalDateTime lastModified) { + } + + public void createAppDirectory(App app); + + public void createDirectory(App app, Path path); + + public void createFile(App app, Path path, byte[] content); + + public void delete(App app, Path path); + + public void get(App app, Path path); + + /** + * Paths in return list are always relative to the appDir. + * + * @param app to list the files for + * @param path in case of {@link Optional#EMPTY} the appDir is used as start dir. If not empty, path has to be relative + * to the appDir + */ + public List list(App app, Optional path); +} diff --git a/files/pom.xml b/files/pom.xml index 41c6170..e73b5d4 100644 --- a/files/pom.xml +++ b/files/pom.xml @@ -21,6 +21,10 @@ de.77zzcx7.nbs-cloud web-container-config + + de.77zzcx7.nbs-cloud + files-api + org.springframework.boot diff --git a/files/src/main/java/de/nbscloud/files/AppLocationTracker.java b/files/src/main/java/de/nbscloud/files/AppLocationTracker.java new file mode 100644 index 0000000..c84b62d --- /dev/null +++ b/files/src/main/java/de/nbscloud/files/AppLocationTracker.java @@ -0,0 +1,54 @@ +package de.nbscloud.files; + +import de.nbscloud.files.config.FilesConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.nio.file.Path; + +@Component +public class AppLocationTracker implements InitializingBean { + private static final Logger logger = LoggerFactory.getLogger(AppLocationTracker.class); + + @Autowired + private FilesConfig filesConfig; + @Autowired + private LocationTracker locationTracker; + + private Path baseDirPath; + + public Path resolve(String appId, Path path) { + validate(Path.of(appId)); + validate(path); + + final Path appPath = this.baseDirPath.resolve(appId); + + return appPath.resolve(path); + } + + private void validate(Path path) { + if(path == null) { + throw new IllegalStateException("Null"); + } + + if(path.toString().contains("..")) { + throw new IllegalStateException("Relative path"); + } + + if (path.isAbsolute()) { + throw new IllegalStateException("Absolute path: " + path); + } + + if(!this.baseDirPath.resolve(path).normalize().startsWith(this.baseDirPath)) { + throw new IllegalStateException("Illegal path: " + path); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + this.baseDirPath = this.locationTracker.getAppDir(); + } +} diff --git a/files/src/main/java/de/nbscloud/files/FileSystemService.java b/files/src/main/java/de/nbscloud/files/FileSystemService.java index d698d75..988f2fb 100644 --- a/files/src/main/java/de/nbscloud/files/FileSystemService.java +++ b/files/src/main/java/de/nbscloud/files/FileSystemService.java @@ -1,5 +1,6 @@ package de.nbscloud.files; +import de.nbscloud.files.api.FilesService.ContentContainer; import de.nbscloud.files.config.FilesConfig; import de.nbscloud.files.exception.FileSystemServiceException; import org.apache.commons.io.FileUtils; @@ -20,6 +21,7 @@ import java.time.ZoneId; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -29,10 +31,6 @@ public class FileSystemService { private static final Logger logger = LoggerFactory.getLogger(FileSystemService.class); - public static record ContentContainer(boolean directory, Path path, String name, long size, - LocalDateTime lastModified) { - } - public enum SortOrder { /** * First by type (directories first), then by name ignoring case @@ -95,6 +93,14 @@ public class FileSystemService { } } + void createDirectory(Path path) { + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new FileSystemServiceException("Could not create directory", e); + } + } + public void createFile(String name, byte[] content) { try { Files.write(this.locationTracker.resolve(name), content, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); @@ -278,7 +284,7 @@ public class FileSystemService { } } - public List list(SortOrder sortOrder) { + List list(Path startPath, SortOrder sortOrder, Function relativizer) { try { List contentList = Files.list(this.locationTracker.getCurrentLocation()) .filter(path -> { @@ -295,7 +301,7 @@ public class FileSystemService { final boolean isDir = Files.isDirectory(path); return new ContentContainer(isDir, - this.locationTracker.getRelativeToBaseDir(path), + relativizer.apply(path), path.getFileName().toString(), isDir ? FileUtils.sizeOfDirectory(path.toFile()) : Files.size(path), LocalDateTime.ofInstant(Files.getLastModifiedTime(path) @@ -313,6 +319,10 @@ public class FileSystemService { } } + public List list(SortOrder sortOrder) { + return list(this.locationTracker.getCurrentLocation(), sortOrder, path -> this.locationTracker.getRelativeToBaseDir(path)); + } + public List collectDirs(String sourceFile) { try { final List resultList = new ArrayList<>(); diff --git a/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java b/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java new file mode 100644 index 0000000..1375683 --- /dev/null +++ b/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java @@ -0,0 +1,61 @@ +package de.nbscloud.files; + +import de.nbscloud.files.api.FilesService; +import de.nbscloud.files.exception.FileSystemServiceException; +import de.nbscloud.webcontainer.registry.App; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +@Service +public class FilesServiceImpl implements FilesService { + @Autowired + private FileSystemService fileSystemService; + @Autowired + private AppLocationTracker locationTracker; + + private Path resolve(App app, Path path) { + return this.locationTracker.resolve(app.getId(), path); + } + + @Override + public void createAppDirectory(App app) { + try { + this.fileSystemService.createDirectory(resolve(app, Path.of(""))); + } + catch(FileSystemServiceException e) { + // Ignore, may happen if the dir already exists + } + } + + @Override + public void createDirectory(App app, Path path) { + this.fileSystemService.createDirectory(resolve(app, path)); + } + + @Override + public void createFile(App app, Path path, byte[] content) { + this.fileSystemService.createFile(resolve(app, path), content); + } + + @Override + public void delete(App app, Path path) { + this.fileSystemService.delete(resolve(app, path)); + } + + @Override + public void get(App app, Path path) { + this.fileSystemService.get(resolve(app, path)); + } + + @Override + public List list(App app, Optional path) { + final Path appPath = resolve(app, Path.of("")); + final Path p = path.map(tmpPath -> resolve(app, tmpPath)).orElse(appPath); + + return this.fileSystemService.list(p, FileSystemService.SortOrder.NATURAL, callbackPath -> appPath.relativize(callbackPath)); + } +} diff --git a/files/src/main/java/de/nbscloud/files/LocationTracker.java b/files/src/main/java/de/nbscloud/files/LocationTracker.java index 824d928..078e077 100644 --- a/files/src/main/java/de/nbscloud/files/LocationTracker.java +++ b/files/src/main/java/de/nbscloud/files/LocationTracker.java @@ -3,6 +3,7 @@ package de.nbscloud.files; import de.nbscloud.files.config.FilesConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -10,7 +11,7 @@ import java.nio.file.Path; import java.nio.file.Paths; @Component -public class LocationTracker { +public class LocationTracker implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(LocationTracker.class); @Autowired @@ -25,7 +26,8 @@ public class LocationTracker { this.currentLocation = this.baseDirPath.resolve(""); } - public void init() { + @Override + public void afterPropertiesSet() throws Exception { this.baseDirPath = Paths.get(this.filesConfig.getBaseDir()); this.currentLocation = this.baseDirPath.resolve(""); @@ -35,7 +37,11 @@ public class LocationTracker { public void reset() { logger.debug("Reset location"); - init(); + try { + afterPropertiesSet(); + } catch (Exception e) { + // Cannot happen + } } Path getCurrentLocation() { @@ -50,6 +56,10 @@ public class LocationTracker { return this.baseDirPath.relativize(other); } + public Path getAppDir() { + return this.baseDirPath.resolve(this.filesConfig.getAppDir()); + } + private Path getTrashBin() { return this.baseDirPath.resolve(this.filesConfig.getTrashBinName()); } @@ -72,24 +82,6 @@ public class LocationTracker { this.currentLocation = this.baseDirPath.resolve(navigateTo); } - public void validate(String path) { - // Absolut paths are allowed, however, they have to be under baseDir - - if(path == null) { - throw new IllegalStateException("Null"); - } - - if(path.contains("..")) { - throw new IllegalStateException("Relative path"); - } - - final Path tmpPath = Path.of(path); - - if(!tmpPath.normalize().startsWith(this.baseDirPath)) { - throw new IllegalStateException("Illegal path: " + path); - } - } - private void validate_internal(String navigateTo) { if(navigateTo == null) { throw new IllegalStateException("Null"); diff --git a/files/src/main/java/de/nbscloud/files/config/FilesConfig.java b/files/src/main/java/de/nbscloud/files/config/FilesConfig.java index d59806e..f30e090 100644 --- a/files/src/main/java/de/nbscloud/files/config/FilesConfig.java +++ b/files/src/main/java/de/nbscloud/files/config/FilesConfig.java @@ -14,6 +14,7 @@ public class FilesConfig { private String trashBinName; private int truncateFileNameChars; private String sharesName; + private String appDir; public String getBaseDir() { return baseDir; @@ -62,4 +63,12 @@ public class FilesConfig { public void setSharesName(String sharesName) { this.sharesName = sharesName; } + + public String getAppDir() { + return appDir; + } + + public void setAppDir(String appDir) { + this.appDir = appDir; + } } diff --git a/files/src/main/java/de/nbscloud/files/controller/FilesController.java b/files/src/main/java/de/nbscloud/files/controller/FilesController.java index ddee2a3..6585c62 100644 --- a/files/src/main/java/de/nbscloud/files/controller/FilesController.java +++ b/files/src/main/java/de/nbscloud/files/controller/FilesController.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import de.nbscloud.files.FileSystemService; import de.nbscloud.files.LocationTracker; import de.nbscloud.files.Share; +import de.nbscloud.files.api.FilesService.ContentContainer; import de.nbscloud.files.config.FilesConfig; import de.nbscloud.files.exception.FileSystemServiceException; import de.nbscloud.files.form.RenameForm; @@ -338,12 +339,12 @@ public class FilesController implements InitializingBean { return retList; } - private List getContent(FileSystemService.SortOrder order) { - final List contentList = this.fileSystemService.list(order); + private List getContent(FileSystemService.SortOrder order) { + final List contentList = this.fileSystemService.list(order); if (!this.locationTracker.isBasePath()) { contentList.add(0, - new FileSystemService.ContentContainer(true, + new ContentContainer(true, this.locationTracker.getParent(), "..", 0L, @@ -382,8 +383,6 @@ public class FilesController implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { - this.locationTracker.init(); - if (this.filesConfig.isUseTrashBin()) { try { this.fileSystemService.createDirectory(this.filesConfig.getTrashBinName()); diff --git a/files/src/main/resources/config/files-application.properties b/files/src/main/resources/config/files-application.properties index 91e5e8b..219e3b5 100644 --- a/files/src/main/resources/config/files-application.properties +++ b/files/src/main/resources/config/files-application.properties @@ -25,4 +25,7 @@ nbs-cloud.files.trashBinName=nbs.internal/nbs.trashbin nbs-cloud.files.truncateFileNameChars=130 # Knob to configure the name of the shares directory -nbs-cloud.files.sharesName=nbs.internal/nbs.shares \ No newline at end of file +nbs-cloud.files.sharesName=nbs.internal/nbs.shares + +# Knob to configure the name of the app directory +nbs-cloud.files.appDir=nbs.internal/apps \ No newline at end of file diff --git a/pom.xml b/pom.xml index 36f7179..8e43bf0 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ files + files-api web-container web-container-registry notes @@ -63,6 +64,11 @@ files ${project.version} + + de.77zzcx7.nbs-cloud + files-api + ${project.version} + de.77zzcx7.nbs-cloud web-container-registry @@ -199,4 +205,4 @@ - \ No newline at end of file +