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);
|
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")
|
@GetMapping(path = "/fling/{flingId}/download/{downloadId}")
|
||||||
public ResponseEntity<Resource> downloadFling(@PathVariable Long flingId) throws ArchiveException, IOException {
|
public ResponseEntity<Resource> downloadFling(@PathVariable Long flingId, @PathVariable String downloadId) throws ArchiveException, IOException {
|
||||||
var fling = flingService.findFlingById(flingId).orElseThrow();
|
var fling = flingService.findFlingById(flingId).orElseThrow();
|
||||||
var flingPackage = flingService.packageFling(flingId);
|
var flingPackage = flingService.downloadFling(downloadId);
|
||||||
var stream = new InputStreamResource(flingPackage.getFirst());
|
var stream = new InputStreamResource(flingPackage.getFirst());
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
|
|
|
@ -29,8 +29,12 @@ public class AuthorizationService {
|
||||||
return userAuth.getShareId().equals(shareUrl);
|
return userAuth.getShareId().equals(shareUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean allowFlingAccess(Long flingId) {
|
public boolean allowFlingAccess(Long flingId, FlingToken authentication) {
|
||||||
return false;
|
if(authentication.getGrantedFlingAuthority().getAuthority().equals(FlingAuthority.FLING_OWNER.name())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return authentication.getGrantedFlingAuthority().getFlingId().equals(flingId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean allowFlingAccess(FlingToken authentication, HttpServletRequest request) {
|
public boolean allowFlingAccess(FlingToken authentication, HttpServletRequest request) {
|
||||||
|
|
|
@ -53,18 +53,18 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||||
.csrf().disable()
|
.csrf().disable()
|
||||||
.cors(withDefaults())
|
.cors(withDefaults())
|
||||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
|
|
||||||
// Everybody can try to authenticate
|
// Everybody can try to authenticate
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers("/api/auth/**")
|
.antMatchers("/api/auth/**")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
.and()
|
.and()
|
||||||
|
|
||||||
// We need to go from most specific to more general.
|
// We need to go from most specific to more general.
|
||||||
// Hence, first define user permissions
|
// Hence, first define user permissions
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers(HttpMethod.GET, "/api/fling/{flingId}/download")
|
// TODO: This is still insecure since URLs are not encrypted
|
||||||
.hasAuthority(FlingAuthority.FLING_USER.name())
|
// TODO: iframe requests don't send the bearer, use cookie instead
|
||||||
|
.antMatchers(HttpMethod.GET, "/api/fling/{flingId}/download/{downloadId}")
|
||||||
|
.permitAll()
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**")
|
.antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**")
|
||||||
|
@ -72,13 +72,20 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
// TODO: This is still insecure since URLs are not encrypted
|
// 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")
|
.antMatchers("/api/artifacts/{artifactId}/{downloadId}/download")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.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]+")
|
.regexMatchers(HttpMethod.GET, "\\/api\\/fling\\?(shareId=|flingId=)[a-zA-Z0-9]+")
|
||||||
.access("@authorizationService.allowFlingAccess(authentication, request)")
|
.access("@authorizationService.allowFlingAccess(authentication, request)")
|
||||||
.and()
|
.and()
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers(HttpMethod.GET, "/api/fling/{flingId}/**")
|
||||||
|
.access("@authorizationService.allowFlingAccess(#flingId, authentication)")
|
||||||
|
.and()
|
||||||
// And lastly, the owner is allowed everything
|
// And lastly, the owner is allowed everything
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers("/api/**")
|
.antMatchers("/api/**")
|
||||||
|
|
|
@ -6,12 +6,14 @@ import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.zip.Deflater;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
@ -127,11 +129,12 @@ public class FlingService {
|
||||||
.reduce(0L, (acc, as) -> acc+as);
|
.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 fling = flingRepository.getOne(flingId);
|
||||||
var tempFile = Files.createTempFile(Long.toString(flingId), ".zip");
|
var tempFile = Files.createTempFile(Long.toString(flingId), ".zip");
|
||||||
|
|
||||||
try(var zipStream = new ZipOutputStream(new FileOutputStream(tempFile.toFile()))){
|
try(var zipStream = new ZipOutputStream(new FileOutputStream(tempFile.toFile()))){
|
||||||
|
zipStream.setLevel(Deflater.BEST_SPEED);
|
||||||
for(ArtifactEntity artifactEntity: fling.getArtifacts()) {
|
for(ArtifactEntity artifactEntity: fling.getArtifacts()) {
|
||||||
ZipEntry ze = new ZipEntry(artifactEntity.getName());
|
ZipEntry ze = new ZipEntry(artifactEntity.getName());
|
||||||
zipStream.putNextEntry(ze);
|
zipStream.putNextEntry(ze);
|
||||||
|
@ -148,8 +151,14 @@ public class FlingService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var archiveLength = tempFile.toFile().length();
|
return tempFile.getFileName().toString();
|
||||||
var archiveStream = new FileInputStream(tempFile.toFile());
|
}
|
||||||
|
|
||||||
|
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);
|
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 {flingClient} from '../../util/flingclient';
|
||||||
|
|
||||||
|
import DirectDownload from './DirectDownload';
|
||||||
|
|
||||||
export default function FlingUser() {
|
export default function FlingUser() {
|
||||||
let { shareId } = useParams();
|
let { shareId } = useParams();
|
||||||
let [fling, setFling] = useState({});
|
let [fling, setFling] = useState({});
|
||||||
|
@ -16,7 +18,7 @@ export default function FlingUser() {
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div>
|
<div>
|
||||||
Hello
|
{fling.sharing && fling.sharing.directDownload ? <DirectDownload fling={fling} />: ""}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,3 +302,13 @@ tbody td {
|
||||||
max-width: 7rem;
|
max-width: 7rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************\
|
||||||
|
| DirectDownload |
|
||||||
|
\******************/
|
||||||
|
|
||||||
|
.direct-download-card {
|
||||||
|
@include shadow;
|
||||||
|
width: 12rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue