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; ...@@ -18,6 +18,8 @@ package org.genesys.filerepository.service.ftp;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.ftpserver.ftplet.FtpFile; import org.apache.ftpserver.ftplet.FtpFile;
...@@ -28,20 +30,18 @@ import org.apache.ftpserver.ftplet.FtpFile; ...@@ -28,20 +30,18 @@ import org.apache.ftpserver.ftplet.FtpFile;
public abstract class CanBeAnythingFile implements FtpFile { public abstract class CanBeAnythingFile implements FtpFile {
/** The parent. */ /** The parent. */
private final String parent; private final Path path;
private long date = new Date().getTime();
/** The name. */ protected boolean dir = false;
private final String name;
/** /**
* Instantiates a new can be anything file. * Instantiates a new can be anything file.
* *
* @param parent the parent * @param parent the parent
* @param name the name * @param path2 the name
*/ */
public CanBeAnythingFile(final String parent, final String name) { public CanBeAnythingFile(final Path parent, final String name) {
this.parent = parent; this.path = parent.resolve(name);
this.name = name;
} }
/* /*
...@@ -50,7 +50,7 @@ public abstract class CanBeAnythingFile implements FtpFile { ...@@ -50,7 +50,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/ */
@Override @Override
public String getAbsolutePath() { 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 { ...@@ -59,7 +59,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/ */
@Override @Override
public String getName() { public String getName() {
return name; return path.getFileName().toString();
} }
/* /*
...@@ -77,7 +77,7 @@ public abstract class CanBeAnythingFile implements FtpFile { ...@@ -77,7 +77,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/ */
@Override @Override
public boolean isDirectory() { public boolean isDirectory() {
return true; return dir;
} }
/* /*
...@@ -86,7 +86,7 @@ public abstract class CanBeAnythingFile implements FtpFile { ...@@ -86,7 +86,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/ */
@Override @Override
public boolean isFile() { public boolean isFile() {
return true; return !dir;
} }
/* /*
...@@ -158,7 +158,7 @@ public abstract class CanBeAnythingFile implements FtpFile { ...@@ -158,7 +158,7 @@ public abstract class CanBeAnythingFile implements FtpFile {
*/ */
@Override @Override
public long getLastModified() { 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; ...@@ -21,6 +21,7 @@ import java.util.List;
import org.apache.ftpserver.ftplet.Authority; import org.apache.ftpserver.ftplet.Authority;
import org.apache.ftpserver.ftplet.AuthorizationRequest; import org.apache.ftpserver.ftplet.AuthorizationRequest;
import org.apache.ftpserver.ftplet.User; import org.apache.ftpserver.ftplet.User;
import org.genesys.blocks.security.model.BasicUser;
/** /**
* The Class FtpUser. * The Class FtpUser.
...@@ -45,6 +46,8 @@ public class FtpUser implements User { ...@@ -45,6 +46,8 @@ public class FtpUser implements User {
/** The home directory. */ /** The home directory. */
private String homeDirectory; private String homeDirectory;
BasicUser<?> user;
/** /**
* Instantiates a new ftp user. * Instantiates a new ftp user.
*/ */
...@@ -60,6 +63,12 @@ public class FtpUser implements User { ...@@ -60,6 +63,12 @@ public class FtpUser implements User {
name = username; name = username;
} }
public FtpUser(BasicUser<?> user) {
this.user = user;
setName(user.getUsername());
setEnabled(user.isEnabled() && user.isAccountNonExpired() && user.isAccountNonLocked() && user.isCredentialsNonExpired());
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.apache.ftpserver.ftplet.User#getName() * @see org.apache.ftpserver.ftplet.User#getName()
......
...@@ -27,6 +27,7 @@ import java.util.List; ...@@ -27,6 +27,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.ftpserver.ftplet.AuthenticationFailedException;
import org.apache.ftpserver.ftplet.FileSystemFactory; import org.apache.ftpserver.ftplet.FileSystemFactory;
import org.apache.ftpserver.ftplet.FileSystemView; import org.apache.ftpserver.ftplet.FileSystemView;
import org.apache.ftpserver.ftplet.FtpException; import org.apache.ftpserver.ftplet.FtpException;
...@@ -41,6 +42,7 @@ import org.slf4j.Logger; ...@@ -41,6 +42,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
...@@ -60,13 +62,14 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -60,13 +62,14 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Autowired(required = true) @Autowired(required = true)
private TemporaryBytesManager bytesManager; private TemporaryBytesManager bytesManager;
/** /**
* File. * File.
* *
* @param repositoryFile the repository file * @param repositoryFile the repository file
* @return the repository ftp 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); LOG.trace("Making RepositoryFtpFile repositoryFile={}", repositoryFile);
final RepositoryFtpFile rff = new RepositoryFtpFile(repositoryFile) { final RepositoryFtpFile rff = new RepositoryFtpFile(repositoryFile) {
...@@ -91,6 +94,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -91,6 +94,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override @Override
public boolean delete() { public boolean delete() {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Delete file={}", this.getAbsolutePath()); LOG.info("Delete file={}", this.getAbsolutePath());
try { try {
repositoryService.removeFile(repositoryFile); repositoryService.removeFile(repositoryFile);
...@@ -99,10 +103,12 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -99,10 +103,12 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
LOG.warn(e.getMessage()); LOG.warn(e.getMessage());
return false; return false;
} }
});
} }
@Override @Override
public boolean move(final FtpFile destination) { public boolean move(final FtpFile destination) {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Move file={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath()); LOG.info("Move file={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
try { try {
repositoryService.moveAndRenameFile(repositoryFile, destination.getAbsolutePath()); repositoryService.moveAndRenameFile(repositoryFile, destination.getAbsolutePath());
...@@ -111,18 +117,23 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -111,18 +117,23 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
LOG.warn("Error moving file: {}", e.getMessage()); LOG.warn("Error moving file: {}", e.getMessage());
return false; return false;
} }
});
} }
@Override @Override
public OutputStream createOutputStream(final long offset) throws IOException { public OutputStream createOutputStream(final long offset) throws IOException {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Creating output stream for file={} at offset={}", getAbsolutePath(), offset); LOG.info("Creating output stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createOutputStream(repositoryFile, offset); return bytesManager.createOutputStream(session.user, repositoryFile, offset);
});
} }
@Override @Override
public InputStream createInputStream(final long offset) throws IOException { public InputStream createInputStream(final long offset) throws IOException {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Creating input stream for file={} at offset={}", getAbsolutePath(), offset); LOG.info("Creating input stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createInputStream(repositoryFile, offset); return bytesManager.createInputStream(repositoryFile, offset);
});
} }
}; };
...@@ -143,6 +154,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -143,6 +154,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override @Override
public boolean move(final FtpFile destination) { public boolean move(final FtpFile destination) {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Move directory={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath()); LOG.info("Move directory={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
try { try {
repositoryService.renamePath(this.getAbsolutePath(), destination.getAbsolutePath()); repositoryService.renamePath(this.getAbsolutePath(), destination.getAbsolutePath());
...@@ -151,6 +163,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -151,6 +163,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
LOG.error("Failed to rename directory", e); LOG.error("Failed to rename directory", e);
return false; return false;
} }
});
} }
@Override @Override
...@@ -162,6 +175,12 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -162,6 +175,12 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override @Override
public List<? extends FtpFile> listFiles() { public List<? extends FtpFile> listFiles() {
return FtpRunAs.asFtpUser(session.user, () -> {
return _listFiles();
});
}
private List<? extends FtpFile> _listFiles() {
final String currentPath = getAbsolutePath(); final String currentPath = getAbsolutePath();
LOG.debug("Listing files in path={}", currentPath); LOG.debug("Listing files in path={}", currentPath);
final ArrayList<FtpFile> all = new ArrayList<>(); final ArrayList<FtpFile> all = new ArrayList<>();
...@@ -171,7 +190,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -171,7 +190,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
all.addAll(repositoryService.getFiles(currentPath).stream().peek(rf -> { all.addAll(repositoryService.getFiles(currentPath).stream().peek(rf -> {
// System.err.println("repoFile " + rf.getPath() + " " + // System.err.println("repoFile " + rf.getPath() + " " +
// rf.getOriginalFilename()); // rf.getOriginalFilename());
}).map(rf -> file(rf)).collect(Collectors.toList())); }).map(rf -> file(rf, session)).collect(Collectors.toList()));
try { try {
all.addAll(repositoryService.listPaths(currentPath).stream() all.addAll(repositoryService.listPaths(currentPath).stream()
...@@ -229,6 +248,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -229,6 +248,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override @Override
public boolean delete() { public boolean delete() {
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Delete this={}", getAbsolutePath()); LOG.info("Delete this={}", getAbsolutePath());
if (session.hasTempDir(getAbsolutePath())) { if (session.hasTempDir(getAbsolutePath())) {
session.removeTempDir(getAbsolutePath()); session.removeTempDir(getAbsolutePath());
...@@ -236,6 +256,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -236,6 +256,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
} }
LOG.warn("Not deleting repository folder={}", getAbsolutePath()); LOG.warn("Not deleting repository folder={}", getAbsolutePath());
return false; return false;
});
} }
@Override @Override
...@@ -261,8 +282,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -261,8 +282,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
public FileSystemView createFileSystemView(final User user) throws FtpException { public FileSystemView createFileSystemView(final User user) throws FtpException {
LOG.info("Creating new repository view for {}", user.getName()); LOG.info("Creating new repository view for {}", user.getName());
// TODO Auto-generated method stub RepositoryFileSystemView userView = new RepositoryFileSystemView((FtpUser) user) {
return new RepositoryFileSystemView(user) {
@Override @Override
public FtpFile getFile(final String file) throws FtpException { public FtpFile getFile(final String file) throws FtpException {
...@@ -277,14 +297,25 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -277,14 +297,25 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
} }
try { 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) { } catch (final NoSuchRepositoryFileException e) {
LOG.debug("Making new CanBeAnythingFile path={} name={}", path.getParent().toString(), path.getFileName().toString()); 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 @Override
public boolean mkdir() { public boolean mkdir() {
this.dir = true;
LOG.info("Mkdir path={}", this.getAbsolutePath()); LOG.info("Mkdir path={}", this.getAbsolutePath());
temporaryDirs.add(this.getAbsolutePath()); temporaryDirs.add(this.getAbsolutePath());
return true; return true;
...@@ -302,7 +333,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -302,7 +333,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
public OutputStream createOutputStream(final long offset) throws IOException { public OutputStream createOutputStream(final long offset) throws IOException {
LOG.info("Creating output stream for new file={} at offset={}", getAbsolutePath(), offset); LOG.info("Creating output stream for new file={} at offset={}", getAbsolutePath(), offset);
assert (offset == 0l); 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 ...@@ -310,7 +341,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
private boolean isDirectory(final Path path) { private boolean isDirectory(final Path path) {
try { 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) { } catch (final InvalidRepositoryPathException e) {
LOG.debug("Invalid repository path {}: {}", path, e.getMessage()); LOG.debug("Invalid repository path {}: {}", path, e.getMessage());
return temporaryDirs.stream().filter(longpath -> longpath.equals(path.toString())).findFirst().isPresent(); return temporaryDirs.stream().filter(longpath -> longpath.equals(path.toString())).findFirst().isPresent();
...@@ -318,6 +349,11 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -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 ...@@ -336,7 +372,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
private abstract class RepositoryFileSystemView implements FileSystemView { private abstract class RepositoryFileSystemView implements FileSystemView {
/** The user. */ /** The user. */
protected User user; protected FtpUser user;
/** The username. */ /** The username. */
protected String username; protected String username;
...@@ -355,9 +391,9 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ ...@@ -355,9 +391,9 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
* *
* @param user the user * @param user the user
*/ */
public RepositoryFileSystemView(final User user) { public RepositoryFileSystemView(final FtpUser user) {
username = user.getName();
this.user = user; this.user = user;
username = user.getName();
} }
/** /**
......
...@@ -63,12 +63,13 @@ public class TemporaryBytesManager { ...@@ -63,12 +63,13 @@ public class TemporaryBytesManager {
/** /**
* Create a local file for writing. On stream close push the file to * Create a local file for writing. On stream close push the file to
* {@link BytesStorageService}