1
0

#22 Fix bug with password protected shares

This commit is contained in:
2022-11-01 18:19:16 +01:00
parent 1f5b2c0a5d
commit 9b30e06949
4 changed files with 68 additions and 30 deletions

View File

@@ -88,6 +88,7 @@ public class FileSystemService {
private MimeTypeDetector mimeTypeDetector = new MimeTypeDetector(); private MimeTypeDetector mimeTypeDetector = new MimeTypeDetector();
public Path createDirectory(String name) { public Path createDirectory(String name) {
try { try {
return Files.createDirectories(this.locationTracker.resolve(name)); return Files.createDirectories(this.locationTracker.resolve(name));
} catch (IOException e) { } catch (IOException e) {
@@ -96,6 +97,8 @@ public class FileSystemService {
} }
void createDirectory(Path path) { void createDirectory(Path path) {
this.locationTracker.ensureValidPath(path);
try { try {
Files.createDirectories(path); Files.createDirectories(path);
} catch (IOException e) { } catch (IOException e) {
@@ -104,14 +107,12 @@ public class FileSystemService {
} }
public void createFile(String name, byte[] content) { public void createFile(String name, byte[] content) {
try { createFile(this.locationTracker.resolve(name), content);
Files.write(this.locationTracker.resolve(name), content, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
} catch (IOException e) {
throw new FileSystemServiceException("Could not create file", e);
}
} }
public void createFile(Path path, byte[] content) { public void createFile(Path path, byte[] content) {
this.locationTracker.ensureValidPath(path);
try { try {
Files.write(path, content, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW); Files.write(path, content, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
} catch (IOException e) { } catch (IOException e) {
@@ -120,6 +121,8 @@ public class FileSystemService {
} }
public void overwriteFile(Path path, byte[] content) { public void overwriteFile(Path path, byte[] content) {
this.locationTracker.ensureValidPath(path);
try { try {
Files.write(path, content, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); Files.write(path, content, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
} catch (IOException e) { } catch (IOException e) {
@@ -128,6 +131,9 @@ public class FileSystemService {
} }
public Path move(Path originalPath, Path newPath) { public Path move(Path originalPath, Path newPath) {
this.locationTracker.ensureValidPath(originalPath);
this.locationTracker.ensureValidPath(newPath);
try { try {
return Files.move(originalPath, newPath, StandardCopyOption.ATOMIC_MOVE); return Files.move(originalPath, newPath, StandardCopyOption.ATOMIC_MOVE);
} catch (IOException e) { } catch (IOException e) {
@@ -136,15 +142,12 @@ public class FileSystemService {
} }
public boolean delete(String name) { public boolean delete(String name) {
try { return delete(this.locationTracker.resolve(name));
// TODO does only delete dirs if they are empty - but maybe we want that?
return Files.deleteIfExists(this.locationTracker.resolve(name));
} catch (IOException e) {
throw new FileSystemServiceException("Could not delete file", e);
}
} }
public boolean delete(Path path) { public boolean delete(Path path) {
this.locationTracker.ensureValidPath(path);
try { try {
// TODO does only delete dirs if they are empty - but maybe we want that? // TODO does only delete dirs if they are empty - but maybe we want that?
return Files.deleteIfExists(path); return Files.deleteIfExists(path);
@@ -154,6 +157,8 @@ public class FileSystemService {
} }
public byte[] get(Path path) { public byte[] get(Path path) {
this.locationTracker.ensureValidPath(path);
try { try {
return Files.readAllBytes(path); return Files.readAllBytes(path);
} catch (IOException e) { } catch (IOException e) {
@@ -170,6 +175,8 @@ public class FileSystemService {
} }
public void stream(Path targetPath, ZipOutputStream outputStream) { public void stream(Path targetPath, ZipOutputStream outputStream) {
this.locationTracker.ensureValidPath(targetPath);
try { try {
Files.walkFileTree(targetPath, new SimpleFileVisitor<Path>() { Files.walkFileTree(targetPath, new SimpleFileVisitor<Path>() {
@Override @Override
@@ -198,14 +205,12 @@ public class FileSystemService {
} }
public InputStream stream(String name) { public InputStream stream(String name) {
try { return stream(this.locationTracker.resolve(name));
return Files.newInputStream(this.locationTracker.resolve(name));
} catch (IOException e) {
throw new FileSystemServiceException("Could not stream file", e);
}
} }
private InputStream stream(Path name) { public InputStream stream(Path name) {
this.locationTracker.ensureValidPath(name);
try { try {
return Files.newInputStream(name); return Files.newInputStream(name);
} catch (IOException e) { } catch (IOException e) {
@@ -214,18 +219,16 @@ public class FileSystemService {
} }
public long getSize(String name) { public long getSize(String name) {
try { return getSize(this.locationTracker.resolve(name));
return Files.size(this.locationTracker.resolve(name));
} catch (IOException e) {
throw new FileSystemServiceException("Could not get file", e);
}
} }
public long getSize(Path name) { public long getSize(Path name) {
this.locationTracker.ensureValidPath(name);
try { try {
return Files.size(name); return Files.size(name);
} catch (IOException e) { } catch (IOException e) {
throw new FileSystemServiceException("Could not get file", e); throw new FileSystemServiceException("Could not get file size", e);
} }
} }
@@ -258,10 +261,16 @@ public class FileSystemService {
} }
public String getMimeType(String name) { public String getMimeType(String name) {
try { return getMimeType(this.locationTracker.resolve(name));
final String detectedMimeType = this.mimeTypeDetector.detectMimeType(this.locationTracker.resolve(name)); }
logger.debug("Detected mime type {} for file {}", detectedMimeType, name); public String getMimeType(Path path) {
this.locationTracker.ensureValidPath(path);
try {
final String detectedMimeType = this.mimeTypeDetector.detectMimeType(path);
logger.debug("Detected mime type {} for file {}", detectedMimeType, path);
return detectedMimeType; return detectedMimeType;
} catch (GetBytesException e) { } catch (GetBytesException e) {
@@ -295,6 +304,8 @@ public class FileSystemService {
} }
List<ContentContainer> list(Path startPath, SortOrder sortOrder, Function<Path, Path> relativizer) { List<ContentContainer> list(Path startPath, SortOrder sortOrder, Function<Path, Path> relativizer) {
this.locationTracker.ensureValidPath(startPath);
try { try {
List<ContentContainer> contentList = Files.list(startPath) List<ContentContainer> contentList = Files.list(startPath)
.filter(path -> { .filter(path -> {
@@ -330,6 +341,8 @@ public class FileSystemService {
} }
ContentTree getTree(Path startPath, SortOrder sortOrder, Function<Path, Path> relativizer) { ContentTree getTree(Path startPath, SortOrder sortOrder, Function<Path, Path> relativizer) {
this.locationTracker.ensureValidPath(startPath);
try { try {
if (!Files.isDirectory(startPath)) { if (!Files.isDirectory(startPath)) {
return null; return null;
@@ -399,6 +412,9 @@ public class FileSystemService {
} }
List<String> collectDirs(Path sourcePath, Path baseDir, Function<Path, Path> relativizer, boolean includeSource) { List<String> collectDirs(Path sourcePath, Path baseDir, Function<Path, Path> relativizer, boolean includeSource) {
this.locationTracker.ensureValidPath(sourcePath);
this.locationTracker.ensureValidPath(baseDir);
try { try {
final List<String> resultList = new ArrayList<>(); final List<String> resultList = new ArrayList<>();
final boolean sourceIsDir = Files.isDirectory(sourcePath); final boolean sourceIsDir = Files.isDirectory(sourcePath);

View File

@@ -106,6 +106,25 @@ public class LocationTracker implements InitializingBean {
} }
} }
public void ensureValidPath(Path path) {
// The provided systemd unit file restricts access to the nbscloud/ directory
// but better safe than sorry - user could roll their own service after all
if(path == null) {
throw new IllegalStateException("Null");
}
if(path.toString().contains("..")) {
throw new IllegalStateException("Relative path");
}
// Regardless of where we navigate to, we must never leave the base directory
// The normalize() call is important, as it resolves (possible) relative paths
if(!path.normalize().startsWith(this.baseDirPath)) {
throw new IllegalStateException("Illegal path: " + path);
}
}
public Path resolve(String name) { public Path resolve(String name) {
validate_internal(name); validate_internal(name);

View File

@@ -344,9 +344,9 @@ public class FilesController implements InitializingBean {
return ResponseEntity.ok() return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
.header(HttpHeaders.CONTENT_TYPE, this.fileSystemService.getMimeType(filename)) .header(HttpHeaders.CONTENT_TYPE, this.fileSystemService.getMimeType(sharedFilePath))
.header(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(filename))) .header(HttpHeaders.CONTENT_LENGTH, String.valueOf(this.fileSystemService.getSize(sharedFilePath)))
.body(new InputStreamResource(new ObservableInputStream(this.fileSystemService.stream(filename), new ObservableInputStream.Observer() { .body(new InputStreamResource(new ObservableInputStream(this.fileSystemService.stream(sharedFilePath), new ObservableInputStream.Observer() {
@Override @Override
public void closed() throws IOException { public void closed() throws IOException {
if (share.isOneTime()) { if (share.isOneTime()) {

View File

@@ -1,5 +1,8 @@
v19:
- #22 Fix a bug with password protected shares
v18: v18:
- Password protected shares - #22 Password protected shares
- Basic Note app implementation - Basic Note app implementation
- Files app now offers API for other apps to store files - Files app now offers API for other apps to store files