User upload and Dropzone
Let users with rights to open fling also upload files if allowUpload set to true for the fling
This commit is contained in:
parent
4d9b3d0d87
commit
18f035b7c2
5 changed files with 256 additions and 13 deletions
|
@ -10,16 +10,19 @@ import org.springframework.stereotype.Service;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.friedl.fling.security.authentication.FlingToken;
|
import net.friedl.fling.security.authentication.FlingToken;
|
||||||
import net.friedl.fling.security.authentication.dto.UserAuthDto;
|
import net.friedl.fling.security.authentication.dto.UserAuthDto;
|
||||||
|
import net.friedl.fling.service.ArtifactService;
|
||||||
import net.friedl.fling.service.FlingService;
|
import net.friedl.fling.service.FlingService;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class AuthorizationService {
|
public class AuthorizationService {
|
||||||
private FlingService flingService;
|
private FlingService flingService;
|
||||||
|
private ArtifactService artifactService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public AuthorizationService(FlingService flingService) {
|
public AuthorizationService(FlingService flingService, ArtifactService artifactService) {
|
||||||
this.flingService = flingService;
|
this.flingService = flingService;
|
||||||
|
this.artifactService = artifactService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean allowUpload(Long flingId, FlingToken authentication) {
|
public boolean allowUpload(Long flingId, FlingToken authentication) {
|
||||||
|
@ -27,8 +30,14 @@ public class AuthorizationService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return flingService.findFlingById(flingId).orElseThrow().getAllowUpload()
|
var uploadAllowed = flingService.findFlingById(flingId).orElseThrow().getAllowUpload();
|
||||||
&& authentication.getGrantedFlingAuthority().getFlingId().equals(flingId);
|
|
||||||
|
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) {
|
public boolean allowFlingAccess(UserAuthDto userAuth, String shareUrl) {
|
||||||
|
@ -57,7 +66,8 @@ public class AuthorizationService {
|
||||||
? flingService.findFlingByShareId(shareId).orElseThrow().getId()
|
? flingService.findFlingByShareId(shareId).orElseThrow().getId()
|
||||||
: Long.parseLong(request.getParameter("flingId"));
|
: Long.parseLong(request.getParameter("flingId"));
|
||||||
} catch (NumberFormatException | NoSuchElementException e) {
|
} 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;
|
flingId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,10 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||||
.antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**")
|
.antMatchers(HttpMethod.POST, "/api/artifacts/{flingId}/**")
|
||||||
.access("@authorizationService.allowUpload(#flingId, authentication)")
|
.access("@authorizationService.allowUpload(#flingId, authentication)")
|
||||||
.and()
|
.and()
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers(HttpMethod.PATCH, "/api/artifacts/{artifactId}")
|
||||||
|
.access("@authorizationService.allowPatchingArtifact(#artifactId, authentication)")
|
||||||
|
.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
|
// TODO: iframe requests don't send the bearer, use cookie instead
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default () => {
|
||||||
<OwnerRoute path="/admin/:fling"><FlingAdmin /></OwnerRoute>
|
<OwnerRoute path="/admin/:fling"><FlingAdmin /></OwnerRoute>
|
||||||
|
|
||||||
<Route exact path="/unlock" component={Unlock} />
|
<Route exact path="/unlock" component={Unlock} />
|
||||||
<UserRoute exact path="/f/:shareId"><FlingUser /></UserRoute>
|
<UserRoute path="/f/:shareId"><FlingUser /></UserRoute>
|
||||||
|
|
||||||
<Route match="*">Not implemented</Route>
|
<Route match="*">Not implemented</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
|
@ -19,7 +19,9 @@ export default function FlingUser() {
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div>
|
<div>
|
||||||
{fling.sharing && fling.sharing.directDownload ? <DirectDownload fling={fling} />: <FlingUserList fling={fling} />}
|
{fling.sharing && fling.sharing.directDownload
|
||||||
|
? <DirectDownload fling={fling} />
|
||||||
|
: <FlingUserList fling={fling} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import React, {useState, useEffect, useRef} from 'react';
|
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 {flingClient, artifactClient} from '../../util/flingclient';
|
||||||
|
|
||||||
|
import upload from '../resources/upload.svg';
|
||||||
|
import drop from '../resources/drop.svg';
|
||||||
|
|
||||||
function Artifacts(props) {
|
function Artifacts(props) {
|
||||||
let [artifacts, setArtifacts] = useState([]);
|
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(
|
||||||
|
<div className="column col-6 col-md-12 mb-2">
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-header">
|
||||||
|
<i className="icon icon-cross float-right c-hand" onClick={ev => deleteFile(idx)}/>
|
||||||
|
<div className="card-title h5">{file.name}</div>
|
||||||
|
<div className="card-subtitle text-gray">{(new Date(file.lastModified)).toLocaleString()+", "+readableBytes(file.size)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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<fileList.length; i++) { arr.push(fileList[i]); }
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOnDragEnter(ev) {
|
||||||
|
stopEvent(ev);
|
||||||
|
if(dragCount === 0) setDragging(true);
|
||||||
|
|
||||||
|
setDragCount(dragCount+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOnDragLeave(ev) {
|
||||||
|
stopEvent(ev);
|
||||||
|
let dc = dragCount;
|
||||||
|
|
||||||
|
dc -= 1;
|
||||||
|
setDragCount(dc);
|
||||||
|
|
||||||
|
if(dc === 0) setDragging(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopEvent(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function logFiles() {
|
||||||
|
log.info("Files so far: ["+files.map((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(
|
||||||
|
<>
|
||||||
|
<img className="dropzone-icon" alt="dropzone icon" src={drop} />
|
||||||
|
<h5 className="text-primary">Drop now!</h5>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}else {
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<img className="dropzone-icon-upload" alt="dropzone icon" src={upload} />
|
||||||
|
<h5>Click or Drop</h5>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className="container">
|
||||||
|
{logFiles()}
|
||||||
|
<div className="columns">
|
||||||
|
<div className="column col-4 col-sm-12">
|
||||||
|
<div className="dropzone c-hand py-2"
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onClick={handleClick}
|
||||||
|
onDragOver={stopEvent}
|
||||||
|
onDragEnter={handleOnDragEnter}
|
||||||
|
onDragLeave={handleOnDragLeave}>
|
||||||
|
|
||||||
|
<input className="d-hide" ref={fileInputRef} type="file" multiple onChange={handleFileInputChange} />
|
||||||
|
{zoneContent(dragging)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="column col-8 col-sm-12" >
|
||||||
|
<div className="file-list">
|
||||||
|
<div className="row">
|
||||||
|
<div className="container">
|
||||||
|
<div className="columns">
|
||||||
|
{fileList()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="upload-command-line m-2">
|
||||||
|
<span className="total-upload">Total Size: {totalSize()}</span>
|
||||||
|
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function FlingUserList(props) {
|
export default function FlingUserList(props) {
|
||||||
|
let location = useLocation();
|
||||||
|
|
||||||
let iframeContainer = useRef(null);
|
let iframeContainer = useRef(null);
|
||||||
let [infoText, setInfoText] = useState("");
|
let [infoText, setInfoText] = useState("");
|
||||||
let [inProgress, setInProgress] = useState(false);
|
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(
|
return(
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<div className="container-center">
|
<div className="container-center">
|
||||||
|
|
||||||
<div className="col-6 col-xl-8 col-lg-10 col-md-12">
|
<div className="col-6 col-xl-8 col-lg-10 col-md-12">
|
||||||
|
@ -129,11 +352,11 @@ export default function FlingUserList(props) {
|
||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<ul className="tab mx-2">
|
<ul className="tab mx-2">
|
||||||
<li className="tab-item active">
|
<li className={`tab-item ${location.pathname !== path("upload") ? "active": ""}`}>
|
||||||
<a>Files</a>
|
<Link to={path("files")}>Files</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="tab-item">
|
<li className={`tab-item ${location.pathname === path("upload") ? "active": ""}`}>
|
||||||
<a>Upload</a>
|
<Link to={path("upload")}>Upload</Link>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li className="tab-item tab-action">
|
<li className="tab-item tab-action">
|
||||||
|
@ -150,7 +373,11 @@ export default function FlingUserList(props) {
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<Artifacts fling={props.fling} />
|
<Switch>
|
||||||
|
<Route exact path="/f/:shareId"><Artifacts fling={props.fling} /></Route>
|
||||||
|
<Route path="/f/:shareId/files"><Artifacts fling={props.fling} /></Route>
|
||||||
|
<Route path="/f/:shareId/upload"><Upload fling={props.fling} /></Route>
|
||||||
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue