Commit e1fc24c2 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '27-ftp-and-spring-security' into 'master'

Resolve "FTP and Spring Security"

Closes #27

See merge request !24
parents a431f13d 08036e3f
......@@ -18,6 +18,8 @@ package org.genesys.filerepository.service.ftp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Date;
import java.util.List;
import org.apache.ftpserver.ftplet.FtpFile;
......@@ -28,20 +30,18 @@ import org.apache.ftpserver.ftplet.FtpFile;
public abstract class CanBeAnythingFile implements FtpFile {
/** The parent. */
private final String parent;
/** The name. */
private final String name;
private final Path path;
private long date = new Date().getTime();
protected boolean dir = false;
/**
* Instantiates a new can be anything file.
*
* @param parent the parent
* @param name the name
* @param path2 the name
*/
public CanBeAnythingFile(final String parent, final String name) {
this.parent = parent;
this.name = name;
public CanBeAnythingFile(final Path parent, final String name) {
this.path = parent.resolve(name);
}
/*
......@@ -50,7 +50,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/
@Override
public String getAbsolutePath() {
return parent.length() > 1 ? parent.concat("/").concat(name) : parent.concat(name);
return path.toAbsolutePath().toString();
}
/*
......@@ -59,7 +59,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/
@Override
public String getName() {
return name;
return path.getFileName().toString();
}
/*
......@@ -77,7 +77,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/
@Override
public boolean isDirectory() {
return true;
return dir;
}
/*
......@@ -86,7 +86,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/
@Override
public boolean isFile() {
return true;
return !dir;
}
/*
......@@ -158,7 +158,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/
@Override
public long getLastModified() {
return 0;
return date;
}
/*
......
/*
* Copyright 2018 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.genesys.filerepository.service.ftp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* RunAs for FTP
*
* @author Matija Obreza
*/
public class FtpRunAs {
/** The Constant LOG. */
public static final Logger LOG = LoggerFactory.getLogger(FtpRunAs.class);
/**
* NoArgMethod.
*
* @param <R> the return type
* @param <T> the exception type
*/
public static interface NoArgMethod<R, T extends Throwable> {
R run() throws T;
}
/**
* Run method as the ftp user. Switches Spring security context to
* {@link FtpUser#user} and back to what it was.
*
* @param <R> method return type
* @param <T> exception type
* @param ftpUser the ftp user
* @param runnable the code to execute as FTP user
* @return the result of runnable
* @throws T the exception
*/
public static <R, T extends Throwable> R asFtpUser(FtpUser ftpUser, NoArgMethod<R, T> runnable) throws T {
final Authentication prevAuth = SecurityContextHolder.getContext().getAuthentication();
if (ftpUser != null && ftpUser.user != null) {
LOG.trace("Switching to {}", ftpUser.user.getUsername());
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(ftpUser.user, ftpUser.getPassword(), ftpUser.user.getAuthorities()));
}
try {
return runnable.run();
} finally {
if (ftpUser != null && ftpUser.user != null) {
LOG.trace("Switching back from {}", ftpUser.user.getUsername());
}
SecurityContextHolder.getContext().setAuthentication(prevAuth);
}
}
}
......@@ -21,6 +21,7 @@ import java.util.List;
import org.apache.ftpserver.ftplet.Authority;
import org.apache.ftpserver.ftplet.AuthorizationRequest;
import org.apache.ftpserver.ftplet.User;
import org.genesys.blocks.security.model.BasicUser;
/**
* The Class FtpUser.
......@@ -45,6 +46,8 @@ public class FtpUser implements User {
/** The home directory. */
private String homeDirectory;
BasicUser<?> user;
/**
* Instantiates a new ftp user.
*/
......@@ -60,6 +63,12 @@ public class FtpUser implements User {
name = username;
}
public FtpUser(BasicUser<?> user) {
this.user = user;
setName(user.getUsername());
setEnabled(user.isEnabled() && user.isAccountNonExpired() && user.isAccountNonLocked() && user.isCredentialsNonExpired());
}
/*
* (non-Javadoc)
* @see org.apache.ftpserver.ftplet.User#getName()
......
......@@ -27,6 +27,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.ftpserver.ftplet.AuthenticationFailedException;
import org.apache.ftpserver.ftplet.FileSystemFactory;
import org.apache.ftpserver.ftplet.FileSystemView;
import org.apache.ftpserver.ftplet.FtpException;
......@@ -41,6 +42,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
/**
......@@ -60,13 +62,14 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Autowired(required = true)
private TemporaryBytesManager bytesManager;
/**
* File.
*
* @param repositoryFile the repository file
* @return the repository ftp file
*/
private RepositoryFtpFile file(final RepositoryFile repositoryFile) {
private RepositoryFtpFile file(final RepositoryFile repositoryFile, final RepositoryFileSystemView session) {
LOG.trace("Making RepositoryFtpFile repositoryFile={}", repositoryFile);
final RepositoryFtpFile rff = new RepositoryFtpFile(repositoryFile) {
......@@ -91,6 +94,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
public boolean delete() {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Delete file={}", this.getAbsolutePath());
try {
repositoryService.removeFile(repositoryFile);
......@@ -99,10 +103,12 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
LOG.warn(e.getMessage());
return false;
}
});
}
@Override
public boolean move(final FtpFile destination) {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Move file={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
try {
repositoryService.moveAndRenameFile(repositoryFile, destination.getAbsolutePath());
......@@ -111,18 +117,23 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
LOG.warn("Error moving file: {}", e.getMessage());
return false;
}
});
}
@Override
public OutputStream createOutputStream(final long offset) throws IOException {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Creating output stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createOutputStream(repositoryFile, offset);
return bytesManager.createOutputStream(session.user, repositoryFile, offset);
});
}
@Override
public InputStream createInputStream(final long offset) throws IOException {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Creating input stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createInputStream(repositoryFile, offset);
});
}
};
......@@ -143,6 +154,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
public boolean move(final FtpFile destination) {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Move directory={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
try {
repositoryService.renamePath(this.getAbsolutePath(), destination.getAbsolutePath());
......@@ -151,6 +163,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
LOG.error("Failed to rename directory", e);
return false;
}
});
}
@Override
......@@ -162,6 +175,12 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
public List<? extends FtpFile> listFiles() {
return FtpRunAs.asFtpUser(session.user, () -> {
return _listFiles();
});
}
private List<? extends FtpFile> _listFiles() {
final String currentPath = getAbsolutePath();
LOG.debug("Listing files in path={}", currentPath);
final ArrayList<FtpFile> all = new ArrayList<>();
......@@ -171,7 +190,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
all.addAll(repositoryService.getFiles(currentPath).stream().peek(rf -> {
// System.err.println("repoFile " + rf.getPath() + " " +
// rf.getOriginalFilename());
}).map(rf -> file(rf)).collect(Collectors.toList()));
}).map(rf -> file(rf, session)).collect(Collectors.toList()));
try {
all.addAll(repositoryService.listPaths(currentPath).stream()
......@@ -229,6 +248,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
public boolean delete() {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Delete this={}", getAbsolutePath());
if (session.hasTempDir(getAbsolutePath())) {
session.removeTempDir(getAbsolutePath());
......@@ -236,6 +256,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
}
LOG.warn("Not deleting repository folder={}", getAbsolutePath());
return false;
});
}
@Override
......@@ -261,8 +282,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
public FileSystemView createFileSystemView(final User user) throws FtpException {
LOG.info("Creating new repository view for {}", user.getName());
// TODO Auto-generated method stub
return new RepositoryFileSystemView(user) {
RepositoryFileSystemView userView = new RepositoryFileSystemView((FtpUser) user) {
@Override
public FtpFile getFile(final String file) throws FtpException {
......@@ -277,14 +297,25 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
}
try {
return isDirectory(path) ? directory(path.toString(), this) : file(repositoryService.getFile(path.getParent().toString(), path.getFileName().toString()));
return isDirectory(path) ?
// directory
directory(path.toString(), this)
// or file
: file(FtpRunAs.asFtpUser(user, () -> repositoryService.getFile(
path.getParent().toString(),
path.getFileName().toString())), this);
} catch (final AuthenticationException e) {
LOG.warn("Authentication problem {}", e.getMessage(), e);
throw new AuthenticationFailedException(e.getMessage());
} catch (final NoSuchRepositoryFileException e) {
LOG.debug("Making new CanBeAnythingFile path={} name={}", path.getParent().toString(), path.getFileName().toString());
return new CanBeAnythingFile(path.getParent().toString(), path.getFileName().toString()) {
return new CanBeAnythingFile(path.getParent(), path.getFileName().toString()) {
@Override
public boolean mkdir() {
this.dir = true;
LOG.info("Mkdir path={}", this.getAbsolutePath());
temporaryDirs.add(this.getAbsolutePath());
return true;
......@@ -302,7 +333,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
public OutputStream createOutputStream(final long offset) throws IOException {
LOG.info("Creating output stream for new file={} at offset={}", getAbsolutePath(), offset);
assert (offset == 0l);
return bytesManager.newFile(Paths.get(getAbsolutePath()));
return bytesManager.newFile(user, Paths.get(getAbsolutePath()));
}
};
}
......@@ -310,7 +341,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
private boolean isDirectory(final Path path) {
try {
return repositoryService.hasPath(path) || temporaryDirs.stream().filter(longpath -> longpath.equals(path.toString())).findFirst().isPresent();
return path.toString().equals("/") || repositoryService.hasPath(path) || temporaryDirs.stream().filter(longpath -> longpath.equals(path.toString())).findFirst().isPresent();
} catch (final InvalidRepositoryPathException e) {
LOG.debug("Invalid repository path {}: {}", path, e.getMessage());
return temporaryDirs.stream().filter(longpath -> longpath.equals(path.toString())).findFirst().isPresent();
......@@ -318,6 +349,11 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
}
};
return userView;
// AspectJProxyFactory factory = new AspectJProxyFactory(userView);
// factory.addAspect(new FtpSpringSecurityAspect((FtpUser) user));
// return factory.getProxy();
}
/*
......@@ -336,7 +372,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
private abstract class RepositoryFileSystemView implements FileSystemView {
/** The user. */
protected User user;
protected FtpUser user;
/** The username. */
protected String username;
......@@ -355,9 +391,9 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
*
* @param user the user
*/
public RepositoryFileSystemView(final User user) {
username = user.getName();
public RepositoryFileSystemView(final FtpUser user) {
this.user = user;
username = user.getName();
}
/**
......
......@@ -63,12 +63,13 @@ public class TemporaryBytesManager {
/**
* Create a local file for writing. On stream close push the file to
* {@link BytesStorageService}
* @param user
*
* @param path the path
* @return the output stream
* @throws IOException Signals that an I/O exception has occurred.
*/
public OutputStream newFile(final Path path) throws IOException {
public OutputStream newFile(FtpUser user, final Path path) throws IOException {
final String parent = path.getParent().toString();
final String filename = path.getFileName().toString();
......@@ -85,6 +86,7 @@ public class TemporaryBytesManager {
@Override
protected void synchronizeWithRepository() throws IOException {
FtpRunAs.asFtpUser(user, () -> {
final byte[] bytes = readTempFileToBytes(tempFile);
try {
repositoryService.addFile(parent, filename, null, bytes, null);
......@@ -93,6 +95,8 @@ public class TemporaryBytesManager {
throw new IOException(e);
}
LOG.info("Synchronized file={} with repository path={} originalFilename={}", tempFile.getAbsolutePath(), parent, filename);
return null;
});
}
};
}
......@@ -176,7 +180,7 @@ public class TemporaryBytesManager {
* @return the output stream
* @throws IOException Signals that an I/O exception has occurred.
*/
public OutputStream createOutputStream(final RepositoryFile repositoryFile, final long offset) throws IOException {
public OutputStream createOutputStream(final FtpUser user, final RepositoryFile repositoryFile, final long offset) throws IOException {
final File tempFile = shadowRepositoryFile(repositoryFile);
LOG.info("Preparing local copy of rf={} offset={} temp={}", repositoryFile.getUuid(), offset, tempFile.getAbsolutePath());
......@@ -196,6 +200,7 @@ public class TemporaryBytesManager {
@Override
protected void synchronizeWithRepository() throws IOException {
FtpRunAs.asFtpUser(user, () -> {
final byte[] bytes = readTempFileToBytes(tempFile);
LOG.info("Stream to temporary upload buffer is closed, synchronizing bytes={}", bytes.length);
try {
......@@ -205,6 +210,8 @@ public class TemporaryBytesManager {
throw new IOException(e);
}
LOG.info("Synchronized file={} with repository path={} originalFilename={}", tempFile.getAbsolutePath(), repositoryFile.getPath(), repositoryFile.getOriginalFilename());
return null;
});
}
};
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment