Commit 7c85407d authored by Matija Obreza's avatar Matija Obreza

Execute FTP calls with Spring Security context configured for FtpUser

parent a431f13d
/*
* 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,38 +94,46 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
public boolean delete() {
LOG.info("Delete file={}", this.getAbsolutePath());
try {
repositoryService.removeFile(repositoryFile);
return true;
} catch (NoSuchRepositoryFileException | IOException e) {
LOG.warn(e.getMessage());
return false;
}
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Delete file={}", this.getAbsolutePath());
try {
repositoryService.removeFile(repositoryFile);
return true;
} catch (NoSuchRepositoryFileException | IOException e) {
LOG.warn(e.getMessage());
return false;
}
});
}
@Override
public boolean move(final FtpFile destination) {
LOG.info("Move file={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
try {
repositoryService.moveAndRenameFile(repositoryFile, destination.getAbsolutePath());
return true;
} catch (InvalidRepositoryPathException | InvalidRepositoryFileDataException e) {
LOG.warn("Error moving file: {}", e.getMessage());
return false;
}
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Move file={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
try {
repositoryService.moveAndRenameFile(repositoryFile, destination.getAbsolutePath());
return true;
} catch (InvalidRepositoryPathException | InvalidRepositoryFileDataException e) {
LOG.warn("Error moving file: {}", e.getMessage());
return false;
}
});
}
@Override
public OutputStream createOutputStream(final long offset) throws IOException {
LOG.info("Creating output stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createOutputStream(repositoryFile, offset);
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Creating output stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createOutputStream(session.user, repositoryFile, offset);
});
}
@Override
public InputStream createInputStream(final long offset) throws IOException {
LOG.info("Creating input stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createInputStream(repositoryFile, offset);
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Creating input stream for file={} at offset={}", getAbsolutePath(), offset);
return bytesManager.createInputStream(repositoryFile, offset);
});
}
};
......@@ -143,14 +154,16 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
public boolean move(final FtpFile destination) {
LOG.info("Move directory={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
try {
repositoryService.renamePath(this.getAbsolutePath(), destination.getAbsolutePath());
return true;
} catch (final InvalidRepositoryPathException e) {
LOG.error("Failed to rename directory", e);
return false;
}
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Move directory={} to dest={}", this.getAbsolutePath(), destination.getAbsolutePath());
try {
repositoryService.renamePath(this.getAbsolutePath(), destination.getAbsolutePath());
return true;
} catch (final InvalidRepositoryPathException e) {
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,13 +248,15 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
public boolean delete() {
LOG.info("Delete this={}", getAbsolutePath());
if (session.hasTempDir(getAbsolutePath())) {
session.removeTempDir(getAbsolutePath());
return true;
}
LOG.warn("Not deleting repository folder={}", getAbsolutePath());
return false;
return FtpRunAs.asFtpUser(session.user, () -> {
LOG.info("Delete this={}", getAbsolutePath());
if (session.hasTempDir(getAbsolutePath())) {
session.removeTempDir(getAbsolutePath());
return true;
}
LOG.warn("Not deleting repository folder={}", getAbsolutePath());
return false;
});
}
@Override
......@@ -250,7 +271,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
return rfd;
}
/*
* (non-Javadoc)
* @see
......@@ -260,9 +281,8 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
@Override
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,7 +297,17 @@ 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(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());
......@@ -302,7 +332,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 +340,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 +348,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 +371,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,7 +390,7 @@ public class RepositoryFileSystemFactory implements FileSystemFactory, Initializ
*
* @param user the user
*/
public RepositoryFileSystemView(final User user) {
public RepositoryFileSystemView(final FtpUser user) {
username = user.getName();
this.user = user;
}
......
......@@ -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,14 +86,17 @@ public class TemporaryBytesManager {
@Override
protected void synchronizeWithRepository() throws IOException {
final byte[] bytes = readTempFileToBytes(tempFile);
try {
repositoryService.addFile(parent, filename, null, bytes, null);
} catch (InvalidRepositoryPathException | InvalidRepositoryFileDataException e) {
LOG.warn("Error synchronizing new file parent={} filename={} with repository: {}", parent, filename, e.getMessage());
throw new IOException(e);
}
LOG.info("Synchronized file={} with repository path={} originalFilename={}", tempFile.getAbsolutePath(), parent, filename);
FtpRunAs.asFtpUser(user, () -> {
final byte[] bytes = readTempFileToBytes(tempFile);
try {
repositoryService.addFile(parent, filename, null, bytes, null);
} catch (InvalidRepositoryPathException | InvalidRepositoryFileDataException e) {
LOG.warn("Error synchronizing new file parent={} filename={} with repository: {}", parent, filename, e.getMessage());
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,15 +200,18 @@ public class TemporaryBytesManager {
@Override
protected void synchronizeWithRepository() throws IOException {
final byte[] bytes = readTempFileToBytes(tempFile);
LOG.info("Stream to temporary upload buffer is closed, synchronizing bytes={}", bytes.length);
try {
repositoryService.updateBytes(repositoryFile, null, bytes);
} catch (final NoSuchRepositoryFileException e) {
LOG.warn("Error synchronizing new file with repository: {}", e.getMessage());
throw new IOException(e);
}
LOG.info("Synchronized file={} with repository path={} originalFilename={}", tempFile.getAbsolutePath(), repositoryFile.getPath(), repositoryFile.getOriginalFilename());
FtpRunAs.asFtpUser(user, () -> {
final byte[] bytes = readTempFileToBytes(tempFile);
LOG.info("Stream to temporary upload buffer is closed, synchronizing bytes={}", bytes.length);
try {
repositoryService.updateBytes(repositoryFile, null, bytes);
} catch (final NoSuchRepositoryFileException e) {
LOG.warn("Error synchronizing new file with repository: {}", e.getMessage());
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