Direct download
Implement direct download with online packaging
This commit is contained in:
parent
4618cc9bff
commit
c1171e8376
7 changed files with 104 additions and 13 deletions
|
@ -69,11 +69,15 @@ public class FlingController {
|
|||
flingService.deleteFlingById(flingId);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/fling/{flingId}/package")
|
||||
public String packageFling(@PathVariable Long flingId) throws IOException, ArchiveException {
|
||||
return flingService.packageFling(flingId);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/fling/{flingId}/download")
|
||||
public ResponseEntity<Resource> downloadFling(@PathVariable Long flingId) throws ArchiveException, IOException {
|
||||
@GetMapping(path = "/fling/{flingId}/download/{downloadId}")
|
||||
public ResponseEntity<Resource> downloadFling(@PathVariable Long flingId, @PathVariable String downloadId) throws ArchiveException, IOException {
|
||||
var fling = flingService.findFlingById(flingId).orElseThrow();
|
||||
var flingPackage = flingService.packageFling(flingId);
|
||||
var flingPackage = flingService.downloadFling(downloadId);
|
||||
var stream = new InputStreamResource(flingPackage.getFirst());
|
||||
|
||||
return ResponseEntity.ok()
|
||||
|
|
|
@ -29,8 +29,12 @@ public class AuthorizationService {
|
|||
return userAuth.getShareId().equals(shareUrl);
|
||||
}
|
||||
|
||||
public boolean allowFlingAccess(Long flingId) {
|
||||
return false;
|
||||
public boolean allowFlingAccess(Long flingId, FlingToken authentication) {
|
||||
if(authentication.getGrantedFlingAuthority().getAuthority().equals(FlingAuthority.FLING_OWNER.name())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return authentication.getGrantedFlingAuthority().getFlingId().equals(flingId);
|
||||
}
|
||||
|
||||
public boolean allowFlingAccess(FlingToken authentication, HttpServletRequest request) {
|
||||
|
|
|
@ -53,18 +53,18 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
|||
.csrf().disable()
|
||||
.cors(withDefaults())
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
|
||||
// Everybody can try to authenticate
|
||||
.authorizeRequests()
|
||||
.antMatchers("/api/auth/**")
|
||||
.permitAll()
|
||||
.and()
|
||||
|
||||
// We need to go from most specific to more general.
|
||||
// Hence, first define user permissions
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.GET, "/api/fling/{flingId}/download")
|
||||
.hasAuthority(FlingAuthority.FLING_USER.name())
|
||||
// TODO: This is still insecure since URLs are not encrypted
|
||||
// TODO: iframe requests don't send the bearer, use cookie instead
|
||||
.antMatchers(HttpMethod.GET, "/api/fling/{flingId}/download/{downloadId}")
|
||||
.permitAll()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**")
|
||||
|
@ -72,13 +72,20 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
|||
.and()
|
||||
.authorizeRequests()
|
||||
// TODO: This is still insecure since URLs are not encrypted
|
||||
// TODO: iframe requests don't send the bearer, use cookie instead
|
||||
.antMatchers("/api/artifacts/{artifactId}/{downloadId}/download")
|
||||
.permitAll()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
// TODO: Security by request parameters is just not well supported with spring security
|
||||
// TODO: Change API
|
||||
.regexMatchers(HttpMethod.GET, "\\/api\\/fling\\?(shareId=|flingId=)[a-zA-Z0-9]+")
|
||||
.access("@authorizationService.allowFlingAccess(authentication, request)")
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.GET, "/api/fling/{flingId}/**")
|
||||
.access("@authorizationService.allowFlingAccess(#flingId, authentication)")
|
||||
.and()
|
||||
// And lastly, the owner is allowed everything
|
||||
.authorizeRequests()
|
||||
.antMatchers("/api/**")
|
||||
|
|
|
@ -6,12 +6,14 @@ import java.io.FileOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
|
@ -127,11 +129,12 @@ public class FlingService {
|
|||
.reduce(0L, (acc, as) -> acc+as);
|
||||
}
|
||||
|
||||
public Pair<InputStream, Long> packageFling(Long flingId) throws IOException, ArchiveException {
|
||||
public String packageFling(Long flingId) throws IOException, ArchiveException {
|
||||
var fling = flingRepository.getOne(flingId);
|
||||
var tempFile = Files.createTempFile(Long.toString(flingId), ".zip");
|
||||
|
||||
try(var zipStream = new ZipOutputStream(new FileOutputStream(tempFile.toFile()))){
|
||||
zipStream.setLevel(Deflater.BEST_SPEED);
|
||||
for(ArtifactEntity artifactEntity: fling.getArtifacts()) {
|
||||
ZipEntry ze = new ZipEntry(artifactEntity.getName());
|
||||
zipStream.putNextEntry(ze);
|
||||
|
@ -148,8 +151,14 @@ public class FlingService {
|
|||
}
|
||||
}
|
||||
|
||||
var archiveLength = tempFile.toFile().length();
|
||||
var archiveStream = new FileInputStream(tempFile.toFile());
|
||||
return tempFile.getFileName().toString();
|
||||
}
|
||||
|
||||
public Pair<InputStream, Long> downloadFling(String fileId) throws IOException, ArchiveException {
|
||||
var tempFile = Paths.get(System.getProperty("java.io.tmpdir"), fileId).toFile();
|
||||
|
||||
var archiveLength = tempFile.length();
|
||||
var archiveStream = new FileInputStream(tempFile);
|
||||
|
||||
return Pair.of(archiveStream, archiveLength);
|
||||
}
|
||||
|
|
55
web/fling/src/components/user/DirectDownload.jsx
Normal file
55
web/fling/src/components/user/DirectDownload.jsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import log from 'loglevel';
|
||||
import React, {useRef, useState, useEffect} from 'react';
|
||||
|
||||
import {flingClient} from '../../util/flingclient';
|
||||
|
||||
export default function FlingUser(props) {
|
||||
let iframeContainer = useRef(null);
|
||||
let [packaging, setPackaging] = useState(true);
|
||||
let [waitingMessage, setWaitingMessage] = useState("");
|
||||
let [downloadUrl, setDownloadUrl] = useState("");
|
||||
|
||||
useEffect(handleDownload, []);
|
||||
|
||||
function handleDownload() {
|
||||
flingClient.packageFling(props.fling.id)
|
||||
.then(downloadUrl => {
|
||||
setPackaging(false);
|
||||
// We need this iframe hack because with a regular href, while
|
||||
// the browser downloads the file fine, it also reloads the page, hence
|
||||
// loosing all logs and state
|
||||
let frame = document.createElement("iframe");
|
||||
frame.src = downloadUrl;
|
||||
iframeContainer.current.appendChild(frame);
|
||||
setDownloadUrl(downloadUrl);
|
||||
});
|
||||
|
||||
let randMsg = ["Please stay patient...",
|
||||
"Your download will be ready soon...",
|
||||
"Packaging up your files...",
|
||||
"Almost there..."];
|
||||
setInterval(() => setWaitingMessage(randMsg[Math.floor(Math.random() * randMsg.length)]), 10000);
|
||||
}
|
||||
|
||||
return(
|
||||
<div>
|
||||
<div className="container-center">
|
||||
<div className="card direct-download-card">
|
||||
<div className="card-body ">
|
||||
{packaging
|
||||
? <><div className="loading loading-lg" />
|
||||
{waitingMessage ? waitingMessage: "Packaging up your files..."}
|
||||
</>
|
||||
: <>
|
||||
<h5>Your download is <span className="text-primary">ready!</span></h5>
|
||||
<i className="icon icon-check icon-2x text-primary" /><br/>
|
||||
<span className="text-dark">Download doesn't start? <br/><a href={downloadUrl}>Click here</a></span>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-hide" ref={iframeContainer} />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -5,6 +5,8 @@ import {useParams, BrowserRouter} from 'react-router-dom';
|
|||
|
||||
import {flingClient} from '../../util/flingclient';
|
||||
|
||||
import DirectDownload from './DirectDownload';
|
||||
|
||||
export default function FlingUser() {
|
||||
let { shareId } = useParams();
|
||||
let [fling, setFling] = useState({});
|
||||
|
@ -16,7 +18,7 @@ export default function FlingUser() {
|
|||
|
||||
return(
|
||||
<div>
|
||||
Hello
|
||||
{fling.sharing && fling.sharing.directDownload ? <DirectDownload fling={fling} />: ""}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -302,3 +302,13 @@ tbody td {
|
|||
max-width: 7rem;
|
||||
}
|
||||
}
|
||||
|
||||
/******************\
|
||||
| DirectDownload |
|
||||
\******************/
|
||||
|
||||
.direct-download-card {
|
||||
@include shadow;
|
||||
width: 12rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue