1
0

#22 Password protected shares

This commit is contained in:
2022-10-20 21:11:05 +02:00
parent 2ab4497bd1
commit 4188a86995
11 changed files with 176 additions and 10 deletions

View File

@@ -7,6 +7,7 @@ public class Share {
private boolean oneTime;
private LocalDate expiryDate;
private String path;
private String password; // clear text, as protecting it provides no additional security
public String getUuid() {
return uuid;
@@ -39,4 +40,12 @@ public class Share {
public void setPath(String path) {
this.path = path;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -8,18 +8,21 @@ 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.PasswordForm;
import de.nbscloud.files.form.RenameForm;
import de.nbscloud.files.form.ShareForm;
import de.nbscloud.webcontainer.MessageHelper;
import de.nbscloud.webcontainer.registry.AppRegistry;
import de.nbscloud.webcontainer.shared.config.WebContainerSharedConfig;
import org.apache.commons.io.input.ObservableInputStream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
@@ -148,12 +151,11 @@ public class FilesController implements InitializingBean {
final Path targetPath = this.locationTracker.resolve(filename);
try {
if(Files.isDirectory(targetPath)) {
if (Files.isDirectory(targetPath)) {
downloadDirectory(targetPath, filename, response);
return ResponseEntity.ok(":)");
}
else {
} else {
return downloadSingleFile(filename);
}
} catch (RuntimeException | IOException e) {
@@ -218,7 +220,7 @@ public class FilesController implements InitializingBean {
@GetMapping("files/share")
public String share(Model model, String filename) {
model.addAttribute("currentLocation", getCurrentLocationPrefixed());
model.addAttribute("form", new ShareForm(filename, false, LocalDate.now().plusDays(1)));
model.addAttribute("form", new ShareForm(filename, false, LocalDate.now().plusDays(1), null));
model.addAttribute("apps", this.appRegistry.getAll());
model.addAttribute("filename", filename);
this.webContainerSharedConfig.addDefaults(model);
@@ -229,7 +231,8 @@ public class FilesController implements InitializingBean {
@PostMapping("/files/doShare")
public String doShare(@RequestParam("filename") String filename,
@RequestParam(value = "oneTime", defaultValue = "false") boolean oneTime,
@RequestParam(value = "expiryDate", required = false) String expiryDateString
@RequestParam(value = "expiryDate", required = false) String expiryDateString,
@RequestParam(value = "password", required = false) String password
) {
final Path filePath = this.locationTracker.getRelativeToBaseDir(this.locationTracker.resolve(filename));
final String shareUuid = UUID.randomUUID().toString();
@@ -240,10 +243,15 @@ public class FilesController implements InitializingBean {
.map(ed -> LocalDate.parse(ed, DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.orElse(null);
if(StringUtils.isEmpty(password)) {
password = null; // if no real password has been provided assume no password
}
share.setUuid(shareUuid);
share.setPath(filePath.toString());
share.setExpiryDate(expiryDate);
share.setOneTime(oneTime);
share.setPassword(password);
try {
final String shareJson = mapper.writeValueAsString(share);
@@ -262,6 +270,64 @@ public class FilesController implements InitializingBean {
@GetMapping("files/shares")
public ResponseEntity shares(String shareUuid) {
try {
final String shareJson = new String(this.fileSystemService.get(this.locationTracker.resolveShare(shareUuid)));
final Share share = mapper.readValue(shareJson, Share.class);
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
final HttpHeaders headers = new HttpHeaders();
headers.add("Location", "shares/passwordCheck?shareUuid=" + shareUuid);
return new ResponseEntity<String>(headers, HttpStatus.FOUND);
}
else {
return doShares(shareUuid);
}
} catch (RuntimeException | JsonProcessingException e) {
logger.error("Could not get shared file", e);
return ResponseEntity.internalServerError().body(":(");
}
}
@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")
public ResponseEntity checkPassword(@RequestParam(value = "shareUuid", required = true) String shareUuid,
@RequestParam(value = "password", required = true) String password) {
try {
final String shareJson = new String(this.fileSystemService.get(this.locationTracker.resolveShare(shareUuid)));
final Share share = mapper.readValue(shareJson, Share.class);
if(share.getPassword().equals(password)) {
return doShares(shareUuid);
}
else {
this.messageHelper.addResolvableError("nbscloud.files.share.error.passwordWrong");
final HttpHeaders headers = new HttpHeaders();
headers.add("Location", "passwordCheck?shareUuid=" + shareUuid);
return new ResponseEntity<String>(headers, HttpStatus.FOUND);
}
} catch (RuntimeException | JsonProcessingException e) {
logger.error("Could not get shared file", e);
return ResponseEntity.internalServerError().body(":(");
}
}
private ResponseEntity doShares(String shareUuid) {
try {
final String shareJson = new String(this.fileSystemService.get(this.locationTracker.resolveShare(shareUuid)));
final Share share = mapper.readValue(shareJson, Share.class);

View File

@@ -0,0 +1,27 @@
package de.nbscloud.files.form;
public class PasswordForm {
private String shareUuid;
private String password;
public PasswordForm(String shareUuid, String password) {
this.shareUuid = shareUuid;
this.password = password;
}
public String getShareUuid() {
return shareUuid;
}
public void setShareUuid(String shareUuid) {
this.shareUuid = shareUuid;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -7,10 +7,13 @@ public class ShareForm {
private boolean oneTime;
private LocalDate expiryDate;
public ShareForm(String filename, boolean oneTime, LocalDate expiryDate) {
private String password;
public ShareForm(String filename, boolean oneTime, LocalDate expiryDate, String password) {
this.filename = filename;
this.oneTime = oneTime;
this.expiryDate = expiryDate;
this.password = password;
}
public String getFilename() {
@@ -36,4 +39,12 @@ public class ShareForm {
public void setExpiryDate(LocalDate expiryDate) {
this.expiryDate = expiryDate;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -20,9 +20,15 @@ nbscloud.files.share.title=nbscloud - files\: share \u0020
nbscloud.files.share.label.filename=Filename\:
nbscloud.files.share.label.onetime=One time\:
nbscloud.files.share.label.expirydate=Expiry date\:
nbscloud.files.share.label.password=Password\:
nbscloud.files.share.submit=Share
nbscloud.files.share-message=Shared file\:\u0020
nbscloud.files.share.error.passwordWrong=Wrong password!
nbscloud.files.share.password.title=Password check
nbscloud.files.share.password.submit=Check password
nbscloud.files.share.password.label.password=Password\:
nbscloud.files.file-disk-usage-widget.heading=files disk usage
nbscloud.files.file-disk-usage-widget-table.basedir=Base dir\:

View File

@@ -20,9 +20,15 @@ nbscloud.files.share.title=nbscloud - Dateien\: teilen \u0020
nbscloud.files.share.label.filename=Dateiname\:
nbscloud.files.share.label.onetime=Einmaliger Abruf\:
nbscloud.files.share.label.expirydate=Ablaufdatum\:
nbscloud.files.share.label.password=Passwort\:
nbscloud.files.share.submit=Teilen
nbscloud.files.share-message=Datei geteilt\:\u0020
nbscloud.files.share.error.passwordWrong=Passwort falsch!
nbscloud.files.share.password.title=Passwort eingeben
nbscloud.files.share.password.submit=Passwort pr\u00FCfen
nbscloud.files.share.password.label.password=Passwort\:
nbscloud.files.file-disk-usage-widget.heading=Dateien Festplattennutzung
nbscloud.files.file-disk-usage-widget-table.basedir=Verzeichnis\:

View File

@@ -49,13 +49,15 @@
}
#rename-form,
#share-form {
#share-form,
#share-password-form {
margin-top: 3em;
margin-left: 3em;
}
#rename-form *,
#share-form * {
#share-form *,
#share-password-form * {
display: block;
margin-top: 1em;
box-sizing: border-box;
@@ -69,7 +71,8 @@
}
#rename-form > input[type=text],
#share-form > input[type=text] {
#share-form > input[type=text],
#share-password-form > input[type=text] {
width: 70em;
}

View File

@@ -0,0 +1,25 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{nbscloud.files.share.password.title}"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link th:if="${darkMode}" rel="stylesheet" th:href="@{/css/darkModeColors.css}"/>
<link th:if="${!darkMode}" rel="stylesheet" th:href="@{/css/lightModeColors.css}"/>
<link rel="stylesheet" th:href="@{/css/main.css}"/>
<link rel="stylesheet" th:href="@{/css/files_main.css}"/>
<link rel="shortcut icon" th:href="@{/favicon.ico}"/>
</head>
<body>
<div id="main-container">
<div th:replace="includes/messages :: messages"/>
<form id="share-password-form" action="#" th:action="@{/files/shares/checkPassword}" th:object="${form}" method="post" enctype="multipart/form-data">
<label for="password" th:text="#{nbscloud.files.share.password.label.password}"/>
<input type="password" id="password" th:field="*{password}" />
<input type="hidden" id="shareUuid" th:field="*{shareUuid}" />
<input type="submit" th:value="#{nbscloud.files.share.password.submit}" />
</form>
<div th:replace="includes/footer :: footer"/>
</div>
</body>
</html>

View File

@@ -20,6 +20,8 @@
<input type="checkbox" id="oneTime" th:field="*{oneTime}" />
<label for="expiryDate" th:text="#{nbscloud.files.share.label.expirydate}"/>
<input type="date" id="expiryDate" th:field="*{expiryDate}"/>
<label for="password" th:text="#{nbscloud.files.share.label.password}"/>
<input type="text" id="password" th:field="*{password}"/>
<input type="submit" th:value="#{nbscloud.files.share.submit}" />
</form>
<div th:replace="includes/footer :: footer"/>