Settings and checks for maximum artifact size
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing

Different archive backends may not cope well with large file sizes.
Additionally, Fling admins may want to restrict the maximum file size to
protect their server.
This commit is contained in:
Armin Friedl 2020-07-26 10:34:05 +02:00
parent d4b7f1db30
commit e424c9ef09
Signed by: armin
GPG key ID: 48C726EEE7FBCBC8
13 changed files with 69 additions and 12 deletions

View file

@ -16,7 +16,6 @@ import net.friedl.fling.model.json.PathSerializer;
@Configuration @Configuration
public class FlingConfiguration { public class FlingConfiguration {
@Bean @Bean
public PasswordEncoder passwordEncoder() { public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();

View file

@ -4,6 +4,7 @@ import java.io.IOException;
import java.util.UUID; import java.util.UUID;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -32,6 +33,8 @@ import net.friedl.fling.service.archive.ArchiveService;
@SecurityRequirement(name = "bearer") @SecurityRequirement(name = "bearer")
@Validated @Validated
public class ArtifactController { public class ArtifactController {
@Value("${fling.max-artifact-size:-1}")
private Long maxArtifactSize;
private ArtifactService artifactService; private ArtifactService artifactService;
private ArchiveService archiveService; private ArchiveService archiveService;
@ -58,6 +61,10 @@ public class ArtifactController {
@PostMapping(path = "/{id}/data") @PostMapping(path = "/{id}/data")
public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request) public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request)
throws IOException { throws IOException {
if(maxArtifactSize >= 0 && maxArtifactSize < request.getContentLengthLong()) {
throw new IOException("Maximum artifact size exceeded");
}
archiveService.storeArtifact(id, request.getInputStream()); archiveService.storeArtifact(id, request.getInputStream());
} }

View file

@ -70,6 +70,12 @@
"type": "java.util.Long", "type": "java.util.Long",
"description": "Time until JWT tokens expire", "description": "Time until JWT tokens expire",
"sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration" "sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration"
},
{
"name": "fling.max-artifact-size",
"type": "java.util.Long",
"description": "Maximum artifact size in bytes. -1 to disable.",
"sourceType": "net.friedl.fling.controller.ArtifactController"
} }
] ]
} }

View file

@ -21,6 +21,7 @@ logging.level:
# spring.http.log-request-details: true # spring.http.log-request-details: true
fling: fling:
max-artifact-size: 209715200 # 200 MB
archive.filesystem.archive-path: /home/armin/Desktop/fling archive.filesystem.archive-path: /home/armin/Desktop/fling
security: security:
allowed-origins: allowed-origins:

View file

@ -16,6 +16,7 @@ logging.level:
root: WARN root: WARN
fling: fling:
max-artifact-size: 209715200 # 200 MB
archive.filesystem.archive-path: "/var/fling/files" archive.filesystem.archive-path: "/var/fling/files"
security: security:
allowed-origins: allowed-origins:

View file

@ -1,2 +1,3 @@
REACT_APP_API=https://fling.friedl.net REACT_APP_API=https://fling.friedl.net
REACT_APP_LOGLEVEL=warn REACT_APP_LOGLEVEL=warn
REACT_APP_FILESIZE=209715200

View file

@ -1,2 +1,4 @@
REACT_APP_API=http://localhost:8080/ REACT_APP_API=http://localhost:8080/
REACT_APP_LOGLEVEL=trace REACT_APP_LOGLEVEL=trace
REACT_APP_FILESIZE=209715200

View file

@ -13376,6 +13376,11 @@
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
}, },
"vanillatoasts": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/vanillatoasts/-/vanillatoasts-1.4.0.tgz",
"integrity": "sha512-DVMPPWVVt/x1B2f4iVpCw3rYDceu1PGRV8ECc3OfUC8By6O14OdXWjKryfaz8UorBuNe8btDIsujp6YP6gDNGA=="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View file

@ -22,7 +22,8 @@
"react-scripts": "3.4.1", "react-scripts": "3.4.1",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"spectre.css": "^0.5.8" "spectre.css": "^0.5.8",
"vanillatoasts": "^1.4.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View file

@ -100,8 +100,10 @@ export default function FlingArtifacts() {
.then(result => { .then(result => {
log.debug(`Got ${result.length} artifacts`); log.debug(`Got ${result.length} artifacts`);
for (let artifact of result) { for (let artifact of result) {
if(artifact.archived) {
artifacts.push(<FlingArtifactRow key={artifact.id} artifact={artifact} reloadArtifactsFn={getArtifacts} />); artifacts.push(<FlingArtifactRow key={artifact.id} artifact={artifact} reloadArtifactsFn={getArtifacts} />);
} }
}
setArtifacts(artifacts); setArtifacts(artifacts);
}); });

View file

@ -1,4 +1,5 @@
import log from 'loglevel'; import log from 'loglevel';
import VanillaToasts from 'vanillatoasts';
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
@ -81,9 +82,21 @@ export default function Upload() {
stopEvent(ev); stopEvent(ev);
ev.persist(); ev.persist();
let evFiles = ev.dataTransfer.files; let maxSize = process.env.REACT_APP_FILESIZE;
let evFiles = fileListToArray(ev.dataTransfer.files);
if (!evFiles) { for (let i = evFiles.length - 1; i >= 0; i--) {
if (maxSize && maxSize >= 0 && evFiles[i].size > maxSize) {
VanillaToasts.create({
title: "Maximum file size exceeded",
text: `${evFiles[i].name} exceeds the maximum file size of ${prettifyBytes(maxSize)}`,
type: "warning"
});
evFiles.splice(i, 1);
};
}
if (evFiles.lenght === 0) {
console.warn("Dropzone triggered without files"); console.warn("Dropzone triggered without files");
return; return;
} }
@ -198,6 +211,7 @@ export default function Upload() {
</div> </div>
<div className="upload-command-line m-2"> <div className="upload-command-line m-2">
<span className="total-upload">Total Size: {totalSize()}</span> <span className="total-upload">Total Size: {totalSize()}</span>
<span className="total-upload">{`Max: ${prettifyBytes(process.env.REACT_APP_FILESIZE)}`}</span>
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button> <button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
</div> </div>
</div> </div>

View file

@ -1,4 +1,5 @@
import log from 'loglevel'; import log from 'loglevel';
import VanillaToasts from 'vanillatoasts';
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Switch, Route, useLocation, Link } from "react-router-dom"; import { Switch, Route, useLocation, Link } from "react-router-dom";
@ -17,7 +18,10 @@ function Artifacts(props) {
let flingClient = new FlingClient(); let flingClient = new FlingClient();
flingClient.getArtifacts(props.fling.id) flingClient.getArtifacts(props.fling.id)
.then(artifacts => setArtifacts(artifacts)); .then(artifacts => {
artifacts = artifacts.filter(a => a.archived)
setArtifacts(artifacts)
});
}, [props.fling]); }, [props.fling]);
function renderArtifact(artifact) { function renderArtifact(artifact) {
@ -119,9 +123,21 @@ function Upload(props) {
stopEvent(ev); stopEvent(ev);
ev.persist(); ev.persist();
let evFiles = ev.dataTransfer.files; let maxSize = process.env.REACT_APP_FILESIZE;
let evFiles = fileListToArray(ev.dataTransfer.files);
if (!evFiles) { for (let i = evFiles.length - 1; i >= 0; i--) {
if (maxSize && maxSize >= 0 && evFiles[i].size > maxSize) {
VanillaToasts.create({
title: "Maximum file size exceeded",
text: `${evFiles[i].name} exceeds the maximum file size of ${prettifyBytes(maxSize)}`,
type: "warning"
});
evFiles.splice(i, 1);
};
}
if (evFiles.length === 0) {
console.warn("Dropzone triggered without files"); console.warn("Dropzone triggered without files");
return; return;
} }
@ -236,6 +252,7 @@ function Upload(props) {
</div> </div>
<div className="upload-command-line m-2"> <div className="upload-command-line m-2">
<span className="total-upload">Total Size: {totalSize()}</span> <span className="total-upload">Total Size: {totalSize()}</span>
<span className="total-upload">{`Max: ${prettifyBytes(process.env.REACT_APP_FILESIZE)}`}</span>
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button> <button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
</div> </div>
</div> </div>

View file

@ -14,6 +14,7 @@ import { BrowserRouter } from "react-router-dom";
import App from './App'; import App from './App';
import 'vanillatoasts/vanillatoasts.css';
import "./style/fling.scss"; import "./style/fling.scss";
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';