#2 Folder sharing
This commit is contained in:
@@ -17,6 +17,7 @@ files:
|
|||||||
|[application.properties](./web-container/src/main/resources/config/application.properties)|Main config file providing general app properties|
|
|[application.properties](./web-container/src/main/resources/config/application.properties)|Main config file providing general app properties|
|
||||||
|[shared-application.properties](./web-container-config/src/main/resources/config/shared-application.properties)|Properties shared by all apps|
|
|[shared-application.properties](./web-container-config/src/main/resources/config/shared-application.properties)|Properties shared by all apps|
|
||||||
|[files-application.properties](./files/src/main/resources/config/files-application.properties)|Config file for the files app|
|
|[files-application.properties](./files/src/main/resources/config/files-application.properties)|Config file for the files app|
|
||||||
|
|[dashboard-application.properties](./dashboard/src/main/resources/config/dashboard-application.properties)|Config file for the dashboard app|
|
||||||
|
|
||||||
## Apache httpd config
|
## Apache httpd config
|
||||||
It is advised to not expose NoBullShit-cloud directly - instead a proxy server like Apache httpd should be used to shield access.
|
It is advised to not expose NoBullShit-cloud directly - instead a proxy server like Apache httpd should be used to shield access.
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.annotation.SessionScope;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@SessionScope
|
||||||
public class AppLocationTracker implements InitializingBean {
|
public class AppLocationTracker implements InitializingBean {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AppLocationTracker.class);
|
private static final Logger logger = LoggerFactory.getLogger(AppLocationTracker.class);
|
||||||
|
|
||||||
|
|||||||
@@ -403,6 +403,12 @@ public class FileSystemService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDirectory(Path path) {
|
||||||
|
this.locationTracker.ensureValidPath(path);
|
||||||
|
|
||||||
|
return Files.isDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
public List<ContentContainer> list(SortOrder sortOrder) {
|
public List<ContentContainer> list(SortOrder sortOrder) {
|
||||||
return list(this.locationTracker.getCurrentLocation(), sortOrder, path -> this.locationTracker.getRelativeToBaseDir(path));
|
return list(this.locationTracker.getCurrentLocation(), sortOrder, path -> this.locationTracker.getRelativeToBaseDir(path));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import de.nbscloud.files.exception.FileSystemServiceException;
|
|||||||
import de.nbscloud.webcontainer.registry.App;
|
import de.nbscloud.webcontainer.registry.App;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.context.annotation.SessionScope;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@SessionScope
|
||||||
public class FilesServiceImpl implements FilesService {
|
public class FilesServiceImpl implements FilesService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private FileSystemService fileSystemService;
|
private FileSystemService fileSystemService;
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.annotation.SessionScope;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@SessionScope
|
||||||
public class LocationTracker implements InitializingBean {
|
public class LocationTracker implements InitializingBean {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(LocationTracker.class);
|
private static final Logger logger = LoggerFactory.getLogger(LocationTracker.class);
|
||||||
|
|
||||||
@@ -31,11 +35,11 @@ public class LocationTracker implements InitializingBean {
|
|||||||
this.baseDirPath = Paths.get(this.filesConfig.getBaseDir());
|
this.baseDirPath = Paths.get(this.filesConfig.getBaseDir());
|
||||||
this.currentLocation = this.baseDirPath.resolve("");
|
this.currentLocation = this.baseDirPath.resolve("");
|
||||||
|
|
||||||
logger.info("Initialized location to {}", this.currentLocation);
|
logger.info("{}: Initialized location to {}", id(), this.currentLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
logger.debug("Reset location");
|
logger.debug("{}: Reset location", id());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
afterPropertiesSet();
|
afterPropertiesSet();
|
||||||
@@ -44,6 +48,14 @@ public class LocationTracker implements InitializingBean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void resetCurrentLocation() {
|
||||||
|
this.currentLocation = this.baseDirPath.resolve("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private long id() {
|
||||||
|
return System.identityHashCode(this);
|
||||||
|
}
|
||||||
|
|
||||||
Path getCurrentLocation() {
|
Path getCurrentLocation() {
|
||||||
return this.currentLocation;
|
return this.currentLocation;
|
||||||
}
|
}
|
||||||
@@ -79,7 +91,19 @@ public class LocationTracker implements InitializingBean {
|
|||||||
public void setCurrentLocation(String navigateTo) {
|
public void setCurrentLocation(String navigateTo) {
|
||||||
validate_internal(navigateTo);
|
validate_internal(navigateTo);
|
||||||
|
|
||||||
this.currentLocation = this.baseDirPath.resolve(navigateTo);
|
final Path newLocation = this.baseDirPath.resolve(navigateTo);
|
||||||
|
|
||||||
|
logger.debug("{}: Change location from {} to {}", id(), this.currentLocation, newLocation);
|
||||||
|
|
||||||
|
this.currentLocation = newLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseDirPath(String path) {
|
||||||
|
validate_internal(path);
|
||||||
|
|
||||||
|
this.baseDirPath = this.baseDirPath.resolve(path);
|
||||||
|
|
||||||
|
logger.debug("{}: Change base dir to {}", id(), baseDirPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validate_internal(String navigateTo) {
|
private void validate_internal(String navigateTo) {
|
||||||
|
|||||||
18
files/src/main/java/de/nbscloud/files/SessionInfo.java
Normal file
18
files/src/main/java/de/nbscloud/files/SessionInfo.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package de.nbscloud.files;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.annotation.SessionScope;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@SessionScope
|
||||||
|
public class SessionInfo {
|
||||||
|
private boolean restrictedSession = false;
|
||||||
|
|
||||||
|
public boolean isRestrictedSession() {
|
||||||
|
return restrictedSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRestrictedSession(boolean restrictedSession) {
|
||||||
|
this.restrictedSession = restrictedSession;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import de.nbscloud.files.form.PasswordForm;
|
|||||||
import de.nbscloud.files.form.RenameForm;
|
import de.nbscloud.files.form.RenameForm;
|
||||||
import de.nbscloud.files.form.ShareForm;
|
import de.nbscloud.files.form.ShareForm;
|
||||||
import de.nbscloud.webcontainer.MessageHelper;
|
import de.nbscloud.webcontainer.MessageHelper;
|
||||||
|
import de.nbscloud.files.SessionInfo;
|
||||||
import de.nbscloud.webcontainer.registry.AppRegistry;
|
import de.nbscloud.webcontainer.registry.AppRegistry;
|
||||||
import de.nbscloud.webcontainer.shared.config.WebContainerSharedConfig;
|
import de.nbscloud.webcontainer.shared.config.WebContainerSharedConfig;
|
||||||
import org.apache.commons.io.input.ObservableInputStream;
|
import org.apache.commons.io.input.ObservableInputStream;
|
||||||
@@ -22,7 +23,6 @@ import org.springframework.beans.factory.InitializingBean;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
@@ -37,7 +37,6 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -45,7 +44,6 @@ import java.time.LocalDate;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -70,6 +68,8 @@ public class FilesController implements InitializingBean {
|
|||||||
private ObjectMapper mapper;
|
private ObjectMapper mapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private MessageHelper messageHelper;
|
private MessageHelper messageHelper;
|
||||||
|
@Autowired
|
||||||
|
private SessionInfo sessionInfo;
|
||||||
|
|
||||||
@GetMapping("/files/browse/**")
|
@GetMapping("/files/browse/**")
|
||||||
public String start(Model model, HttpServletRequest httpServletRequest, String sortOrder) {
|
public String start(Model model, HttpServletRequest httpServletRequest, String sortOrder) {
|
||||||
@@ -82,6 +82,7 @@ public class FilesController implements InitializingBean {
|
|||||||
model.addAttribute("content", getContent(order));
|
model.addAttribute("content", getContent(order));
|
||||||
model.addAttribute("sortOrder", order);
|
model.addAttribute("sortOrder", order);
|
||||||
model.addAttribute("apps", this.appRegistry.getAll());
|
model.addAttribute("apps", this.appRegistry.getAll());
|
||||||
|
model.addAttribute("restricted", this.sessionInfo.isRestrictedSession());
|
||||||
this.webContainerSharedConfig.addDefaults(model);
|
this.webContainerSharedConfig.addDefaults(model);
|
||||||
|
|
||||||
return "files/filesIndex";
|
return "files/filesIndex";
|
||||||
@@ -119,6 +120,7 @@ public class FilesController implements InitializingBean {
|
|||||||
model.addAttribute("targetDirs", this.fileSystemService.collectDirs(filename));
|
model.addAttribute("targetDirs", this.fileSystemService.collectDirs(filename));
|
||||||
model.addAttribute("apps", this.appRegistry.getAll());
|
model.addAttribute("apps", this.appRegistry.getAll());
|
||||||
model.addAttribute("filename", filename);
|
model.addAttribute("filename", filename);
|
||||||
|
model.addAttribute("restricted", this.sessionInfo.isRestrictedSession());
|
||||||
this.webContainerSharedConfig.addDefaults(model);
|
this.webContainerSharedConfig.addDefaults(model);
|
||||||
|
|
||||||
return "files/rename";
|
return "files/rename";
|
||||||
@@ -223,6 +225,7 @@ public class FilesController implements InitializingBean {
|
|||||||
model.addAttribute("form", new ShareForm(filename, false, LocalDate.now().plusDays(1), null));
|
model.addAttribute("form", new ShareForm(filename, false, LocalDate.now().plusDays(1), null));
|
||||||
model.addAttribute("apps", this.appRegistry.getAll());
|
model.addAttribute("apps", this.appRegistry.getAll());
|
||||||
model.addAttribute("filename", filename);
|
model.addAttribute("filename", filename);
|
||||||
|
model.addAttribute("restricted", this.sessionInfo.isRestrictedSession());
|
||||||
this.webContainerSharedConfig.addDefaults(model);
|
this.webContainerSharedConfig.addDefaults(model);
|
||||||
|
|
||||||
return "files/share";
|
return "files/share";
|
||||||
@@ -269,22 +272,40 @@ public class FilesController implements InitializingBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("files/shares")
|
@GetMapping("files/shares")
|
||||||
public ResponseEntity shares(String shareUuid) {
|
public Object shares(Model model, String shareUuid) {
|
||||||
|
this.locationTracker.reset();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final String shareJson = new String(this.fileSystemService.get(this.locationTracker.resolveShare(shareUuid)));
|
final String shareJson = new String(this.fileSystemService.get(this.locationTracker.resolveShare(shareUuid)));
|
||||||
final Share share = mapper.readValue(shareJson, Share.class);
|
final Share share = mapper.readValue(shareJson, Share.class);
|
||||||
|
final Path sharedFilePath = this.locationTracker.resolveToBaseDir(share.getPath());
|
||||||
|
final Optional<LocalDate> optExpiryDate = Optional.ofNullable(share.getExpiryDate());
|
||||||
|
final boolean expired = optExpiryDate.map(expiryDate -> LocalDate.now().isAfter(expiryDate)).orElse(false);
|
||||||
|
|
||||||
|
this.sessionInfo.setRestrictedSession(true);
|
||||||
|
|
||||||
if(share.getPassword() != null) {
|
if(share.getPassword() != null) {
|
||||||
// If there is a password check nothing else, so there is no information leak if the remote doesn't know the password
|
model.addAttribute("form", new PasswordForm(shareUuid, null));
|
||||||
final HttpHeaders headers = new HttpHeaders();
|
this.webContainerSharedConfig.addDefaults(model);
|
||||||
|
|
||||||
headers.add("Location", "shares/passwordCheck?shareUuid=" + shareUuid);
|
return "files/checkPassword";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (expired) {
|
||||||
|
fileSystemService.delete(locationTracker.resolveShare(shareUuid));
|
||||||
|
|
||||||
return new ResponseEntity<String>(headers, HttpStatus.FOUND);
|
return ResponseEntity.status(410).body("Share expired!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.fileSystemService.isDirectory(sharedFilePath)) {
|
||||||
|
this.locationTracker.setBaseDirPath(share.getPath());
|
||||||
|
|
||||||
|
return "redirect:/files/browse";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return doShares(shareUuid);
|
return doShares(shareUuid);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (RuntimeException | JsonProcessingException e) {
|
} catch (RuntimeException | JsonProcessingException e) {
|
||||||
logger.error("Could not get shared file", e);
|
logger.error("Could not get shared file", e);
|
||||||
|
|
||||||
@@ -292,33 +313,40 @@ public class FilesController implements InitializingBean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("files/shares/passwordCheck")
|
|
||||||
public String passwordCheck(Model model, String shareUuid) {
|
|
||||||
this.messageHelper.addAndClearAll(model);
|
|
||||||
model.addAttribute("form", new PasswordForm(shareUuid, null));
|
|
||||||
this.webContainerSharedConfig.addDefaults(model);
|
|
||||||
|
|
||||||
return "files/checkPassword";
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/files/shares/checkPassword")
|
@PostMapping("/files/shares/checkPassword")
|
||||||
public ResponseEntity checkPassword(@RequestParam(value = "shareUuid", required = true) String shareUuid,
|
public Object checkPassword(Model model, @RequestParam(value = "shareUuid", required = true) String shareUuid,
|
||||||
@RequestParam(value = "password", required = true) String password) {
|
@RequestParam(value = "password", required = true) String password) {
|
||||||
try {
|
try {
|
||||||
final String shareJson = new String(this.fileSystemService.get(this.locationTracker.resolveShare(shareUuid)));
|
final String shareJson = new String(this.fileSystemService.get(this.locationTracker.resolveShare(shareUuid)));
|
||||||
final Share share = mapper.readValue(shareJson, Share.class);
|
final Share share = mapper.readValue(shareJson, Share.class);
|
||||||
|
final Path sharedFilePath = this.locationTracker.resolveToBaseDir(share.getPath());
|
||||||
|
final Optional<LocalDate> optExpiryDate = Optional.ofNullable(share.getExpiryDate());
|
||||||
|
final boolean expired = optExpiryDate.map(expiryDate -> LocalDate.now().isAfter(expiryDate)).orElse(false);
|
||||||
|
|
||||||
if(share.getPassword().equals(password)) {
|
if(share.getPassword().equals(password)) {
|
||||||
|
if (expired) {
|
||||||
|
fileSystemService.delete(locationTracker.resolveShare(shareUuid));
|
||||||
|
|
||||||
|
return ResponseEntity.status(410).body("Share expired!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.fileSystemService.isDirectory(sharedFilePath)) {
|
||||||
|
this.locationTracker.setBaseDirPath(share.getPath());
|
||||||
|
|
||||||
|
return "redirect:/files/browse";
|
||||||
|
}
|
||||||
|
else {
|
||||||
return doShares(shareUuid);
|
return doShares(shareUuid);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
this.messageHelper.addResolvableError("nbscloud.files.share.error.passwordWrong");
|
this.messageHelper.addResolvableError("nbscloud.files.share.error.passwordWrong");
|
||||||
|
this.messageHelper.addAndClearAll(model);
|
||||||
|
|
||||||
final HttpHeaders headers = new HttpHeaders();
|
model.addAttribute("form", new PasswordForm(shareUuid, null));
|
||||||
|
this.webContainerSharedConfig.addDefaults(model);
|
||||||
|
|
||||||
headers.add("Location", "passwordCheck?shareUuid=" + shareUuid);
|
return "files/checkPassword";
|
||||||
|
|
||||||
return new ResponseEntity<String>(headers, HttpStatus.FOUND);
|
|
||||||
}
|
}
|
||||||
} catch (RuntimeException | JsonProcessingException e) {
|
} catch (RuntimeException | JsonProcessingException e) {
|
||||||
logger.error("Could not get shared file", e);
|
logger.error("Could not get shared file", e);
|
||||||
@@ -333,13 +361,9 @@ public class FilesController implements InitializingBean {
|
|||||||
final Share share = mapper.readValue(shareJson, Share.class);
|
final Share share = mapper.readValue(shareJson, Share.class);
|
||||||
final Path sharedFilePath = this.locationTracker.resolveToBaseDir(share.getPath());
|
final Path sharedFilePath = this.locationTracker.resolveToBaseDir(share.getPath());
|
||||||
final String filename = sharedFilePath.getFileName().toString();
|
final String filename = sharedFilePath.getFileName().toString();
|
||||||
final Optional<LocalDate> optExpiryDate = Optional.ofNullable(share.getExpiryDate());
|
|
||||||
final boolean expired = optExpiryDate.map(expiryDate -> LocalDate.now().isAfter(expiryDate)).orElse(false);
|
|
||||||
|
|
||||||
if (expired) {
|
if(this.fileSystemService.isDirectory(sharedFilePath)) {
|
||||||
fileSystemService.delete(locationTracker.resolveShare(shareUuid));
|
throw new IllegalStateException("Attempt to access folder via file share: " + sharedFilePath);
|
||||||
|
|
||||||
return ResponseEntity.status(410).body("Share expired!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
@@ -381,6 +405,7 @@ public class FilesController implements InitializingBean {
|
|||||||
|
|
||||||
model.addAttribute("files", files);
|
model.addAttribute("files", files);
|
||||||
model.addAttribute("apps", this.appRegistry.getAll());
|
model.addAttribute("apps", this.appRegistry.getAll());
|
||||||
|
model.addAttribute("restricted", this.sessionInfo.isRestrictedSession());
|
||||||
this.webContainerSharedConfig.addDefaults(model);
|
this.webContainerSharedConfig.addDefaults(model);
|
||||||
|
|
||||||
return "files/gallery";
|
return "files/gallery";
|
||||||
@@ -415,8 +440,13 @@ public class FilesController implements InitializingBean {
|
|||||||
if (split.length > 1) {
|
if (split.length > 1) {
|
||||||
this.locationTracker.setCurrentLocation(UriUtils.decode(split[1].substring(1), StandardCharsets.UTF_8));
|
this.locationTracker.setCurrentLocation(UriUtils.decode(split[1].substring(1), StandardCharsets.UTF_8));
|
||||||
} else {
|
} else {
|
||||||
|
if(!this.sessionInfo.isRestrictedSession()) {
|
||||||
this.locationTracker.reset();
|
this.locationTracker.reset();
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
this.locationTracker.resetCurrentLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getCurrentLocation() {
|
private String getCurrentLocation() {
|
||||||
@@ -437,23 +467,23 @@ public class FilesController implements InitializingBean {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
if (this.filesConfig.isUseTrashBin()) {
|
// if (this.filesConfig.isUseTrashBin()) {
|
||||||
try {
|
// try {
|
||||||
this.fileSystemService.createDirectory(this.filesConfig.getTrashBinName());
|
// this.fileSystemService.createDirectory(this.filesConfig.getTrashBinName());
|
||||||
} catch (FileSystemServiceException e) {
|
// } catch (FileSystemServiceException e) {
|
||||||
if (!FileAlreadyExistsException.class.equals(e.getCause().getClass())) {
|
// if (!FileAlreadyExistsException.class.equals(e.getCause().getClass())) {
|
||||||
throw e;
|
// throw e;
|
||||||
}
|
// }
|
||||||
// else: do nothing
|
// // else: do nothing
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
try {
|
// try {
|
||||||
this.fileSystemService.createDirectory(this.filesConfig.getSharesName());
|
// this.fileSystemService.createDirectory(this.filesConfig.getSharesName());
|
||||||
} catch (FileSystemServiceException e) {
|
// } catch (FileSystemServiceException e) {
|
||||||
if (!FileAlreadyExistsException.class.equals(e.getCause().getClass())) {
|
// if (!FileAlreadyExistsException.class.equals(e.getCause().getClass())) {
|
||||||
throw e;
|
// throw e;
|
||||||
}
|
// }
|
||||||
// else: do nothing
|
// // else: do nothing
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,9 +49,9 @@
|
|||||||
<td th:if="${fileEntry.directory}" class="no-mobile">d</td>
|
<td th:if="${fileEntry.directory}" class="no-mobile">d</td>
|
||||||
<td th:if="${!fileEntry.directory}" class="no-mobile">-</td>
|
<td th:if="${!fileEntry.directory}" class="no-mobile">-</td>
|
||||||
<td th:if="${fileEntry.directory}">
|
<td th:if="${fileEntry.directory}">
|
||||||
<a th:if="${fileEntry.name != '..'}" th:href="@{/files/browse/} + ${fileEntry.path}"
|
<a th:if="${fileEntry.name != '..'}" th:href="@{/files/browse/__${fileEntry.path}__}"
|
||||||
th:text="${fileEntry.name}"/>
|
th:text="${fileEntry.name}"/>
|
||||||
<a th:if="${fileEntry.name == '..'}" th:href="@{/files/browse/} + ${fileEntry.path}"
|
<a th:if="${fileEntry.name == '..'}" th:href="@{/files/browse/__${fileEntry.path}__}"
|
||||||
th:text="${fileEntry.name}"/>
|
th:text="${fileEntry.name}"/>
|
||||||
</td>
|
</td>
|
||||||
<td th:if="${!fileEntry.directory && @filesFormatter.needsTruncate(fileEntry.name)}"
|
<td th:if="${!fileEntry.directory && @filesFormatter.needsTruncate(fileEntry.name)}"
|
||||||
@@ -67,11 +67,13 @@
|
|||||||
<details th:if="${fileEntry.name != '..'}" class="files-table-text-align-right">
|
<details th:if="${fileEntry.name != '..'}" class="files-table-text-align-right">
|
||||||
<summary th:text="#{nbscloud.show-actions}"/>
|
<summary th:text="#{nbscloud.show-actions}"/>
|
||||||
<div id="files-content-table-show-actions-detail-container">
|
<div id="files-content-table-show-actions-detail-container">
|
||||||
<div id="files-content-table-show-actions-detail-container-rename">
|
<div id="files-content-table-show-actions-detail-container-rename"
|
||||||
|
th:if="${!restricted}">
|
||||||
<a th:href="@{/files/rename(filename=${fileEntry.name})}"
|
<a th:href="@{/files/rename(filename=${fileEntry.name})}"
|
||||||
th:text="#{nbscloud.files-content-table.table.actions.rename}"/>
|
th:text="#{nbscloud.files-content-table.table.actions.rename}"/>
|
||||||
</div>
|
</div>
|
||||||
<div id="files-content-table-show-actions-detail-container-delete">
|
<div id="files-content-table-show-actions-detail-container-delete"
|
||||||
|
th:if="${!restricted}">
|
||||||
<a th:href="@{/files/delete(filename=${fileEntry.name})}"
|
<a th:href="@{/files/delete(filename=${fileEntry.name})}"
|
||||||
th:text="#{nbscloud.files-content-table.table.actions.delete}"/>
|
th:text="#{nbscloud.files-content-table.table.actions.delete}"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,8 +86,8 @@
|
|||||||
<a th:href="@{/files/preview(filename=${fileEntry.name})}"
|
<a th:href="@{/files/preview(filename=${fileEntry.name})}"
|
||||||
th:text="#{nbscloud.files-content-table.table.actions.preview}"/>
|
th:text="#{nbscloud.files-content-table.table.actions.preview}"/>
|
||||||
</div>
|
</div>
|
||||||
<div th:if="${!fileEntry.directory}"
|
<div id="files-content-table-show-actions-detail-container-share"
|
||||||
id="files-content-table-show-actions-detail-container-share">
|
th:if="${!restricted}">
|
||||||
<a th:href="@{/files/share(filename=${fileEntry.name})}"
|
<a th:href="@{/files/share(filename=${fileEntry.name})}"
|
||||||
th:text="#{nbscloud.files-content-table.table.actions.share}"/>
|
th:text="#{nbscloud.files-content-table.table.actions.share}"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div id="menu-container" th:fragment="menu">
|
<div id="menu-container" th:fragment="menu">
|
||||||
<div class="menu-spacer"></div>
|
<div th:if="${!restricted}" class="menu-spacer"></div>
|
||||||
<div id="upload-container" class="menu-container">
|
<div th:if="${!restricted}" id="upload-container" class="menu-container">
|
||||||
<form method="POST" action="#" th:action="@{/files/upload}" enctype="multipart/form-data">
|
<form method="POST" action="#" th:action="@{/files/upload}" enctype="multipart/form-data">
|
||||||
<!-- JavaScript required unfortunately -->
|
<!-- JavaScript required unfortunately -->
|
||||||
<input id="upload-container-file-input" type="file" name="files" onchange="this.form.submit()"
|
<input id="upload-container-file-input" type="file" name="files" onchange="this.form.submit()"
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
onclick="document.getElementById('upload-container-file-input').click();"></span>
|
onclick="document.getElementById('upload-container-file-input').click();"></span>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-spacer"></div>
|
<div th:if="${!restricted}" class="menu-spacer"></div>
|
||||||
<div id="create-dir-container" class="menu-container">
|
<div th:if="${!restricted}" id="create-dir-container" class="menu-container">
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
<span id="create-dir-container-span-input" class="icon menu-icon"></span>
|
<span id="create-dir-container-span-input" class="icon menu-icon"></span>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<label for="expiryDate" th:text="#{nbscloud.files.share.label.expirydate}"/>
|
<label for="expiryDate" th:text="#{nbscloud.files.share.label.expirydate}"/>
|
||||||
<input type="date" id="expiryDate" th:field="*{expiryDate}"/>
|
<input type="date" id="expiryDate" th:field="*{expiryDate}"/>
|
||||||
<label for="password" th:text="#{nbscloud.files.share.label.password}"/>
|
<label for="password" th:text="#{nbscloud.files.share.label.password}"/>
|
||||||
<input type="text" id="password" th:field="*{password}"/>
|
<input type="password" id="password" th:field="*{password}"/>
|
||||||
<input type="submit" th:value="#{nbscloud.files.share.submit}" />
|
<input type="submit" th:value="#{nbscloud.files.share.submit}" />
|
||||||
</form>
|
</form>
|
||||||
<div th:replace="includes/footer :: footer"/>
|
<div th:replace="includes/footer :: footer"/>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class NotesController implements InitializingBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.messageHelper.addAndClearAll(model);
|
this.messageHelper.addAndClearAll(model);
|
||||||
model.addAttribute("currentNote", notePath);
|
model.addAttribute("currentNote", optNotePath.orElse(Path.of("/")));
|
||||||
model.addAttribute("tree", this.filesService.getTree(app, Optional.empty()));
|
model.addAttribute("tree", this.filesService.getTree(app, Optional.empty()));
|
||||||
model.addAttribute("dirs", this.filesService.collectDirs(app, Optional.empty()));
|
model.addAttribute("dirs", this.filesService.collectDirs(app, Optional.empty()));
|
||||||
model.addAttribute("apps", this.appRegistry.getAll());
|
model.addAttribute("apps", this.appRegistry.getAll());
|
||||||
@@ -124,6 +124,6 @@ public class NotesController implements InitializingBean {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
this.filesService.createAppDirectory(this.app);
|
//this.filesService.createAppDirectory(this.app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ nbscloud.notes.save.success=Saved
|
|||||||
nbscloud.notes.delete.success=Deleted
|
nbscloud.notes.delete.success=Deleted
|
||||||
nbscloud.notes.createDir.success=Directory created
|
nbscloud.notes.createDir.success=Directory created
|
||||||
nbscloud.notes.createNote.success=Note created
|
nbscloud.notes.createNote.success=Note created
|
||||||
|
|
||||||
|
nbscloud.notes.index.title=nbscloud - notes\:\u0020
|
||||||
@@ -5,3 +5,5 @@ nbscloud.notes.save.success=Gespeichert
|
|||||||
nbscloud.notes.delete.success=Gel\u00F6scht
|
nbscloud.notes.delete.success=Gel\u00F6scht
|
||||||
nbscloud.notes.createDir.success=Verzeichnis erstellt
|
nbscloud.notes.createDir.success=Verzeichnis erstellt
|
||||||
nbscloud.notes.createNote.success=Notiz erstellt
|
nbscloud.notes.createNote.success=Notiz erstellt
|
||||||
|
|
||||||
|
nbscloud.notes.index.title=nbscloud - Notizen\:\u0020
|
||||||
@@ -16,5 +16,10 @@
|
|||||||
<groupId>org.springframework</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
<artifactId>spring-context</artifactId>
|
<artifactId>spring-context</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -2,11 +2,13 @@ package de.nbscloud.webcontainer;
|
|||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.context.annotation.SessionScope;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@SessionScope
|
||||||
public class MessageHelper {
|
public class MessageHelper {
|
||||||
// We have to temporarily store messages as we redirect: in some methods
|
// We have to temporarily store messages as we redirect: in some methods
|
||||||
// so everything we add to the model will be gone, that's why we store messages
|
// so everything we add to the model will be gone, that's why we store messages
|
||||||
|
|||||||
@@ -74,6 +74,10 @@
|
|||||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Misc -->
|
<!-- Misc -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -2,10 +2,21 @@ package de.nbscloud.webcontainer;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class Application {
|
public class Application {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(Application.class, args);
|
SpringApplication.run(Application.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.sessionManagement()
|
||||||
|
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
v19:
|
v19:
|
||||||
- #22 Fix a bug with password protected shares
|
- #22 Fix a bug with password protected shares
|
||||||
- Add Apache httpd config example
|
- Add Apache httpd config example
|
||||||
|
- #2 Add folder sharing
|
||||||
|
|
||||||
v18:
|
v18:
|
||||||
- #22 Password protected shares
|
- #22 Password protected shares
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<div id="header-container" th:fragment="header">
|
<div id="header-container" th:fragment="header">
|
||||||
|
<div th:if="${restricted == null || restricted == false}">
|
||||||
<div th:each="app : ${apps}" class="menu-entry">
|
<div th:each="app : ${apps}" class="menu-entry">
|
||||||
<a th:href="@{/} + ${app.startPath}" th:text="${app.id}"/>
|
<a th:href="@{/} + ${app.startPath}" th:text="${app.id}"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user