diff --git a/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java b/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java index 6c06ab4..b4c3f1e 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java +++ b/service/fling/src/main/java/net/friedl/fling/security/AuthorizationService.java @@ -10,16 +10,19 @@ import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; import net.friedl.fling.security.authentication.FlingToken; import net.friedl.fling.security.authentication.dto.UserAuthDto; +import net.friedl.fling.service.ArtifactService; import net.friedl.fling.service.FlingService; @Slf4j @Service public class AuthorizationService { private FlingService flingService; + private ArtifactService artifactService; @Autowired - public AuthorizationService(FlingService flingService) { + public AuthorizationService(FlingService flingService, ArtifactService artifactService) { this.flingService = flingService; + this.artifactService = artifactService; } public boolean allowUpload(Long flingId, FlingToken authentication) { @@ -27,8 +30,14 @@ public class AuthorizationService { return true; } - return flingService.findFlingById(flingId).orElseThrow().getAllowUpload() - && authentication.getGrantedFlingAuthority().getFlingId().equals(flingId); + var uploadAllowed = flingService.findFlingById(flingId).orElseThrow().getAllowUpload(); + + return uploadAllowed && authentication.getGrantedFlingAuthority().getFlingId().equals(flingId); + } + + public boolean allowPatchingArtifact(Long artifactId, FlingToken authentication) { + var flingId = artifactService.findArtifact(artifactId).orElseThrow().getFling().getId(); + return allowUpload(flingId, authentication); } public boolean allowFlingAccess(UserAuthDto userAuth, String shareUrl) { @@ -57,7 +66,8 @@ public class AuthorizationService { ? flingService.findFlingByShareId(shareId).orElseThrow().getId() : Long.parseLong(request.getParameter("flingId")); } catch (NumberFormatException | NoSuchElementException e) { - log.warn("Invalid shareId [shareId=\"{}\"] or flingId [flingId=\"{}\"] found", request.getParameter("shareId"), request.getParameter("flingId")); + log.warn("Invalid shareId [shareId=\"{}\"] or flingId [flingId=\"{}\"] found", + request.getParameter("shareId"), request.getParameter("flingId")); flingId = null; } diff --git a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java index 892139c..158033c 100644 --- a/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java +++ b/service/fling/src/main/java/net/friedl/fling/security/FlingWebSecurityConfigurer.java @@ -70,6 +70,10 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter { .antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**") .access("@authorizationService.allowUpload(#flingId, authentication)") .and() + .authorizeRequests() + .antMatchers(HttpMethod.PATCH, "/api/artifacts/{artifactId}") + .access("@authorizationService.allowPatchingArtifact(#artifactId, authentication)") + .and() .authorizeRequests() // TODO: This is still insecure since URLs are not encrypted // TODO: iframe requests don't send the bearer, use cookie instead diff --git a/web/fling/src/App.jsx b/web/fling/src/App.jsx index 1accc0c..d3dcb83 100644 --- a/web/fling/src/App.jsx +++ b/web/fling/src/App.jsx @@ -22,7 +22,7 @@ export default () => { - + Not implemented diff --git a/web/fling/src/components/user/FlingUser.jsx b/web/fling/src/components/user/FlingUser.jsx index 874efca..4b6dc55 100644 --- a/web/fling/src/components/user/FlingUser.jsx +++ b/web/fling/src/components/user/FlingUser.jsx @@ -19,7 +19,9 @@ export default function FlingUser() { return(
- {fling.sharing && fling.sharing.directDownload ? : } + {fling.sharing && fling.sharing.directDownload + ? + : }
); } diff --git a/web/fling/src/components/user/FlingUserList.jsx b/web/fling/src/components/user/FlingUserList.jsx index bfde251..c61e46a 100644 --- a/web/fling/src/components/user/FlingUserList.jsx +++ b/web/fling/src/components/user/FlingUserList.jsx @@ -1,10 +1,13 @@ import log from 'loglevel'; import React, {useState, useEffect, useRef} from 'react'; -import {useParams, BrowserRouter} from 'react-router-dom'; +import {Switch, Route, Redirect, BrowserRouter, useLocation, useParams, Link} from "react-router-dom"; import {flingClient, artifactClient} from '../../util/flingclient'; +import upload from '../resources/upload.svg'; +import drop from '../resources/drop.svg'; + function Artifacts(props) { let [artifacts, setArtifacts] = useState([]); @@ -60,7 +63,220 @@ function Artifacts(props) { ); } +function Upload(props) { + let fileInputRef = useRef(null); + let [files, setFiles] = useState([]); + let [dragging, setDragging] = useState(false); + let [dragCount, setDragCount] = useState(0); + + useEffect(() => { + // prevent browser from trying to open the file when drag event + // not recognized properly + window.addEventListener("dragover",function(e){ + e.preventDefault(); + },false); + window.addEventListener("drop",function(e){ + e.preventDefault(); + },false); + }); + + function fileList() { + function readableBytes(bytes) { + if(bytes <= 0) return "0 KB"; + + var i = Math.floor(Math.log(bytes) / Math.log(1024)), + sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; + } + + let fileList = []; + files.forEach((file,idx) => { + if(!file.uploaded) { + fileList.push( +
+
+
+ deleteFile(idx)}/> +
{file.name}
+
{(new Date(file.lastModified)).toLocaleString()+", "+readableBytes(file.size)}
+
+
+
+ ); + } + }); + + return fileList; + } + + function deleteFile(idx) { + let f = [...files]; + f.splice(idx, 1); + setFiles(f); + } + + function totalSize() { + function readableBytes(bytes) { + if(bytes <= 0) return "0 KB"; + + var i = Math.floor(Math.log(bytes) / Math.log(1024)), + sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; + } + + let totalSize = 0; + for(let file of files) { + totalSize += file.size; + } + + return readableBytes(totalSize); + } + + function handleClick(ev) { + fileInputRef.current.click(); + } + + function handleFileInputChange(ev) { + let fileInputFiles = fileInputRef.current.files; + if (!fileInputFiles) { + console.warn("No files selected"); + return; + } + + setFiles([...files, ...fileInputFiles]); + } + + function handleDrop(ev) { + stopEvent(ev); + ev.persist(); + + let evFiles = ev.dataTransfer.files; + + if (!evFiles) { + console.warn("Dropzone triggered without files"); + return; + } + + setFiles([...files, ...fileListToArray(evFiles)]); + setDragging(false); + setDragCount(0); + } + + function fileListToArray(fileList) { + if(fileList === undefined || fileList === null) { + return []; + } + + let arr = []; + for (let i=0; i i.name).join(',')+"]"); + } + + function setFileUploaded(idx) { + let f = [...files]; + f[idx].uploaded = true; + setFiles(f); + } + + function handleUpload() { + let f = [...files]; + + files.forEach((file, idx) => { + artifactClient.postArtifact(props.fling.id, file) + .then(response => { + setFileUploaded(idx); + }); + }); + } + + function zoneContent(dragging) { + if(dragging){ + return( + <> + dropzone icon +
Drop now!
+ + ); + }else { + return( + <> + dropzone icon +
Click or Drop
+ + ); + } + } + + return( +
+ {logFiles()} +
+
+
+ + + {zoneContent(dragging)} + +
+
+ +
+
+
+
+
+ {fileList()} +
+
+
+
+ Total Size: {totalSize()} + +
+
+
+
+
+ ); +} + export default function FlingUserList(props) { + let location = useLocation(); + let iframeContainer = useRef(null); let [infoText, setInfoText] = useState(""); let [inProgress, setInProgress] = useState(false); @@ -117,9 +333,16 @@ export default function FlingUserList(props) { }); } + function path(tail) { + if(props.fling && props.fling.sharing) { + return `/f/${props.fling.sharing.shareUrl}/${tail}`; + } + + return ""; + } + return( <> -
@@ -129,11 +352,11 @@ export default function FlingUserList(props) {
    -
  • - Files +
  • + Files
  • -
  • - Upload +
  • + Upload
  • @@ -150,7 +373,11 @@ export default function FlingUserList(props) {
- + + + + +