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;
|
package net.friedl.fling.controller;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
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 org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import net.friedl.fling.model.dto.FlingDto;
|
import net.friedl.fling.model.dto.FlingDto;
|
||||||
|
import net.friedl.fling.persistence.archive.ArchiveException;
|
||||||
import net.friedl.fling.service.FlingService;
|
import net.friedl.fling.service.FlingService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@ -62,4 +68,19 @@ public class FlingController {
|
||||||
public void deleteFling(@PathVariable Long flingId) {
|
public void deleteFling(@PathVariable Long flingId) {
|
||||||
flingService.deleteFlingById(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()
|
.permitAll()
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers("/api/artifacts/{artifactId}/{downloadId}/download")
|
.antMatchers(HttpMethod.GET, "/api/fling/{flingId}/download")
|
||||||
.permitAll()
|
.hasAuthority(FlingAuthority.FLING_USER.name())
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers("/api/**")
|
.antMatchers("/api/**")
|
||||||
.hasAuthority(FlingAuthority.FLING_OWNER.name())
|
.hasAuthority(FlingAuthority.FLING_OWNER.name())
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**")
|
// TODO: This is still insecure since URLs are not encrypted
|
||||||
.access("hasAuthority('"+FlingAuthority.FLING_USER.name()+"') and @authorizationService.allowUpload(#flingId)")
|
.antMatchers("/api/artifacts/{artifactId}/{downloadId}/download")
|
||||||
|
.permitAll()
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers(HttpMethod.GET, "/api/artifacts/**")
|
.antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**")
|
||||||
.hasAuthority(FlingAuthority.FLING_USER.name());
|
.access("hasAuthority('"+FlingAuthority.FLING_USER.name()+"') and @authorizationService.allowUpload(#flingId)");
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,9 @@ public class AuthenticationService {
|
||||||
break;
|
break;
|
||||||
case "user":
|
case "user":
|
||||||
authority = FlingAuthority.FLING_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:
|
default:
|
||||||
throw new BadCredentialsException("Invalid token");
|
throw new BadCredentialsException("Invalid token");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,24 @@
|
||||||
package net.friedl.fling.service;
|
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.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.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.codec.Hex;
|
||||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
@ -18,6 +27,9 @@ import org.springframework.util.StringUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.friedl.fling.model.dto.FlingDto;
|
import net.friedl.fling.model.dto.FlingDto;
|
||||||
import net.friedl.fling.model.mapper.FlingMapper;
|
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.entities.FlingEntity;
|
||||||
import net.friedl.fling.persistence.repositories.FlingRepository;
|
import net.friedl.fling.persistence.repositories.FlingRepository;
|
||||||
|
|
||||||
|
@ -25,16 +37,17 @@ import net.friedl.fling.persistence.repositories.FlingRepository;
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
public class FlingService {
|
public class FlingService {
|
||||||
|
|
||||||
private FlingRepository flingRepository;
|
private FlingRepository flingRepository;
|
||||||
|
|
||||||
private FlingMapper flingMapper;
|
private FlingMapper flingMapper;
|
||||||
|
private Archive archive;
|
||||||
private MessageDigest keyHashDigest;
|
private MessageDigest keyHashDigest;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public FlingService(FlingRepository flingRepository, FlingMapper flingMapper, MessageDigest keyHashDigest) {
|
public FlingService(FlingRepository flingRepository, FlingMapper flingMapper, Archive archive, MessageDigest keyHashDigest) {
|
||||||
this.flingRepository = flingRepository;
|
this.flingRepository = flingRepository;
|
||||||
this.flingMapper = flingMapper;
|
this.flingMapper = flingMapper;
|
||||||
|
this.archive = archive;
|
||||||
this.keyHashDigest = keyHashDigest;
|
this.keyHashDigest = keyHashDigest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +119,41 @@ public class FlingService {
|
||||||
return flingRepository.countArtifactsById(flingId);
|
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() {
|
public String generateShareUrl() {
|
||||||
var key = KeyGenerators
|
var key = KeyGenerators
|
||||||
.secureRandom(16)
|
.secureRandom(16)
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import React, {useState} from 'react';
|
import React, {useState, useEffect} from 'react';
|
||||||
|
|
||||||
import {useParams, BrowserRouter} from 'react-router-dom';
|
import {useParams, BrowserRouter} from 'react-router-dom';
|
||||||
|
|
||||||
export default function FlingAdmin() {
|
import {flingClient} from '../../util/flingclient';
|
||||||
let { fling } = useParams();
|
|
||||||
|
export default function FlingUser() {
|
||||||
|
let { shareId } = useParams();
|
||||||
|
let [fling, setFling] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
flingClient.getFlingByShareId(shareId)
|
||||||
|
.then(f => setFling(f));
|
||||||
|
}, [shareId]);
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div>
|
<div>
|
||||||
|
|
Loading…
Reference in a new issue