Fling packaging and authorization
Allow users with a claim to the fling to download a fling as zip archive. Package all artifacts belonging to a fling as zip file.
This commit is contained in:
parent
0fe28d3db8
commit
f502402cb6
5 changed files with 93 additions and 13 deletions
|
@ -1,8 +1,13 @@
|
|||
package net.friedl.fling.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
@ -15,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import net.friedl.fling.model.dto.FlingDto;
|
||||
import net.friedl.fling.persistence.archive.ArchiveException;
|
||||
import net.friedl.fling.service.FlingService;
|
||||
|
||||
@RestController
|
||||
|
@ -62,4 +68,19 @@ public class FlingController {
|
|||
public void deleteFling(@PathVariable Long flingId) {
|
||||
flingService.deleteFlingById(flingId);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping(path = "/fling/{flingId}/download")
|
||||
public ResponseEntity<Resource> downloadFling(@PathVariable Long flingId) throws ArchiveException, IOException {
|
||||
var fling = flingService.findFlingById(flingId).orElseThrow();
|
||||
var flingPackage = flingService.packageFling(flingId);
|
||||
var stream = new InputStreamResource(flingPackage.getFirst());
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\"" + fling.getName() + ".zip" + "\"")
|
||||
.contentLength(flingPackage.getSecond())
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(stream);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,20 +58,21 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
|||
.permitAll()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/api/artifacts/{artifactId}/{downloadId}/download")
|
||||
.permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/api/fling/{flingId}/download")
|
||||
.hasAuthority(FlingAuthority.FLING_USER.name())
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/api/**")
|
||||
.hasAuthority(FlingAuthority.FLING_OWNER.name())
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**")
|
||||
.access("hasAuthority('"+FlingAuthority.FLING_USER.name()+"') and @authorizationService.allowUpload(#flingId)")
|
||||
// TODO: This is still insecure since URLs are not encrypted
|
||||
.antMatchers("/api/artifacts/{artifactId}/{downloadId}/download")
|
||||
.permitAll()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.GET, "/api/artifacts/**")
|
||||
.hasAuthority(FlingAuthority.FLING_USER.name());
|
||||
.antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**")
|
||||
.access("hasAuthority('"+FlingAuthority.FLING_USER.name()+"') and @authorizationService.allowUpload(#flingId)");
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,9 @@ public class AuthenticationService {
|
|||
break;
|
||||
case "user":
|
||||
authority = FlingAuthority.FLING_USER;
|
||||
flingId = claims.get("fid", Long.class);
|
||||
var sid = claims.get("sid", String.class);
|
||||
flingId = flingService.findFlingByShareId(sid).orElseThrow().getId();
|
||||
break;
|
||||
default:
|
||||
throw new BadCredentialsException("Invalid token");
|
||||
}
|
||||
|
|
|
@ -1,15 +1,24 @@
|
|||
package net.friedl.fling.service;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
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.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -18,6 +27,9 @@ import org.springframework.util.StringUtils;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.friedl.fling.model.dto.FlingDto;
|
||||
import net.friedl.fling.model.mapper.FlingMapper;
|
||||
import net.friedl.fling.persistence.archive.Archive;
|
||||
import net.friedl.fling.persistence.archive.ArchiveException;
|
||||
import net.friedl.fling.persistence.entities.ArtifactEntity;
|
||||
import net.friedl.fling.persistence.entities.FlingEntity;
|
||||
import net.friedl.fling.persistence.repositories.FlingRepository;
|
||||
|
||||
|
@ -25,16 +37,17 @@ import net.friedl.fling.persistence.repositories.FlingRepository;
|
|||
@Service
|
||||
@Transactional
|
||||
public class FlingService {
|
||||
|
||||
private FlingRepository flingRepository;
|
||||
|
||||
private FlingMapper flingMapper;
|
||||
|
||||
private Archive archive;
|
||||
private MessageDigest keyHashDigest;
|
||||
|
||||
@Autowired
|
||||
public FlingService(FlingRepository flingRepository, FlingMapper flingMapper, MessageDigest keyHashDigest) {
|
||||
public FlingService(FlingRepository flingRepository, FlingMapper flingMapper, Archive archive, MessageDigest keyHashDigest) {
|
||||
this.flingRepository = flingRepository;
|
||||
this.flingMapper = flingMapper;
|
||||
this.archive = archive;
|
||||
this.keyHashDigest = keyHashDigest;
|
||||
}
|
||||
|
||||
|
@ -106,6 +119,41 @@ public class FlingService {
|
|||
return flingRepository.countArtifactsById(flingId);
|
||||
}
|
||||
|
||||
public Long getFlingSize(Long flingId) {
|
||||
var fling = flingRepository.getOne(flingId);
|
||||
|
||||
return fling.getArtifacts().stream()
|
||||
.map(ae -> ae.getSize())
|
||||
.reduce(0L, (acc, as) -> acc+as);
|
||||
}
|
||||
|
||||
public Pair<InputStream, Long> 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()))){
|
||||
for(ArtifactEntity artifactEntity: fling.getArtifacts()) {
|
||||
ZipEntry ze = new ZipEntry(artifactEntity.getName());
|
||||
zipStream.putNextEntry(ze);
|
||||
|
||||
var artifactStream = archive.get(artifactEntity.getDoi());
|
||||
try(var archiveEntryStream = new BufferedInputStream(artifactStream)) {
|
||||
int b;
|
||||
while( (b = archiveEntryStream.read()) != -1 ) {
|
||||
zipStream.write(b);
|
||||
}
|
||||
} finally {
|
||||
zipStream.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var archiveLength = tempFile.toFile().length();
|
||||
var archiveStream = new FileInputStream(tempFile.toFile());
|
||||
|
||||
return Pair.of(archiveStream, archiveLength);
|
||||
}
|
||||
|
||||
public String generateShareUrl() {
|
||||
var key = KeyGenerators
|
||||
.secureRandom(16)
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import log from 'loglevel';
|
||||
import React, {useState} from 'react';
|
||||
import React, {useState, useEffect} from 'react';
|
||||
|
||||
import {useParams, BrowserRouter} from 'react-router-dom';
|
||||
|
||||
export default function FlingAdmin() {
|
||||
let { fling } = useParams();
|
||||
import {flingClient} from '../../util/flingclient';
|
||||
|
||||
export default function FlingUser() {
|
||||
let { shareId } = useParams();
|
||||
let [fling, setFling] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
flingClient.getFlingByShareId(shareId)
|
||||
.then(f => setFling(f));
|
||||
}, [shareId]);
|
||||
|
||||
return(
|
||||
<div>
|
||||
|
|
Loading…
Reference in a new issue