Commit 2626d645 authored by Matija Obreza's avatar Matija Obreza

Merge branch '2-file-repository-ftpserver-module' into 'master'

Resolve "file-repository-ftpserver module"

Closes #2

See merge request !4
parents c740b965 0bd01d4b
......@@ -28,6 +28,7 @@ import javax.persistence.ManyToMany;
import javax.persistence.OrderColumn;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.genesys.blocks.model.Copyable;
/**
* {@link ImageGallery} is a collection of ordered {@link RepositoryImage} instances.
......@@ -38,7 +39,7 @@ import org.genesys.blocks.model.AuditedVersionedModel;
* @author mobreza
*/
@Entity(name = "repositorygallery")
public class ImageGallery extends AuditedVersionedModel<ImageGallery> {
public class ImageGallery extends AuditedVersionedModel implements Copyable<ImageGallery> {
private static final long serialVersionUID = 6043583851401365284L;
......@@ -140,9 +141,9 @@ public class ImageGallery extends AuditedVersionedModel<ImageGallery> {
@Override
public ImageGallery apply(ImageGallery source) {
super.apply((AuditedVersionedModel<ImageGallery>) source);
this.description = source.description;
// TODO does this make sense?
this.images = new ArrayList<>(source.images);
this.path = source.path;
this.title = source.title;
......
......@@ -26,8 +26,8 @@ import org.genesys.filerepository.metadata.ImageMetadata;
// TODO: Auto-generated Javadoc
/**
* An {@link RepositoryDocument} is an graphics file in one of the supported image formats (PNG and JPG). It extends the {@link RepositoryFile} by including image-specific metadata
* defined in {@link ImageMetadata}.
* An {@link RepositoryDocument} is an graphics file in one of the supported image formats (PNG and JPG). It extends the
* {@link RepositoryFile} by including image-specific metadata defined in {@link ImageMetadata}.
*
* @author mobreza
*/
......@@ -96,4 +96,12 @@ public class RepositoryDocument extends RepositoryFile implements DocumentMetada
this.abstrct = abstrct;
}
public RepositoryDocument apply(RepositoryDocument source) {
super.apply(source);
this.abstrct = source.abstrct;
this.language = source.language;
return this;
}
}
......@@ -31,6 +31,7 @@ import javax.persistence.TemporalType;
import javax.persistence.Transient;
import org.genesys.blocks.model.AuditedVersionedModel;
import org.genesys.blocks.model.Copyable;
import org.genesys.filerepository.metadata.BaseMetadata;
// TODO: Auto-generated Javadoc
......@@ -40,7 +41,7 @@ import org.genesys.filerepository.metadata.BaseMetadata;
@Entity
@Table(name = "repositoryfile", indexes = { @Index(unique = false, columnList = "path", name = "IX_repoFile_path") })
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class RepositoryFile extends AuditedVersionedModel<RepositoryFile> implements BaseMetadata {
public class RepositoryFile extends AuditedVersionedModel implements BaseMetadata, Copyable<RepositoryFile> {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -4816923593950502695L;
......@@ -569,7 +570,6 @@ public class RepositoryFile extends AuditedVersionedModel<RepositoryFile> implem
@Override
public RepositoryFile apply(RepositoryFile source) {
super.apply((AuditedVersionedModel<RepositoryFile>) source);
this.accessRights = source.accessRights;
this.bibliographicCitation = source.bibliographicCitation;
......
......@@ -15,7 +15,9 @@
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.genesys-pgr</groupId>
......@@ -27,10 +29,43 @@
<description>FTP server for Genesys File Repository</description>
<properties>
<ftpserver.version>1.1.0</ftpserver.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.ftpserver</groupId>
<artifactId>ftpserver-core</artifactId>
<version>${ftpserver.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
<classifier>ftp</classifier>
<scope>test</scope>
</dependency>
</dependencies>
</project>
/*
* Copyright 2017 Global Crop Diversity Trust, www.croptrust.org
*
* 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 java.util.Collections;
import java.util.List;
import org.apache.ftpserver.ftplet.Authority;
import org.apache.ftpserver.ftplet.AuthorizationRequest;
import org.apache.ftpserver.ftplet.User;
public class FtpUser implements User {
private String name;
private String password;
private List<? extends Authority> authorities;
private int maxIdleTime;
private boolean enabled;
private String homeDirectory;
public FtpUser() {
}
public FtpUser(String username) {
name = username;
}
@Override
public String getName() {
return name;
}
@Override
public String getPassword() {
return password;
}
@Override
public List<? extends Authority> getAuthorities() {
return Collections.unmodifiableList(authorities);
}
@Override
public List<? extends Authority> getAuthorities(Class<? extends Authority> clazz) {
return null;
}
@Override
public AuthorizationRequest authorize(AuthorizationRequest request) {
// check for no authorities at all
if (authorities == null) {
return null;
}
boolean someoneCouldAuthorize = false;
for (Authority authority : authorities) {
if (authority.canAuthorize(request)) {
someoneCouldAuthorize = true;
request = authority.authorize(request);
// authorization failed, return null
if (request == null) {
return null;
}
}
}
if (someoneCouldAuthorize) {
return request;
} else {
return null;
}
}
@Override
public int getMaxIdleTime() {
return maxIdleTime;
}
@Override
public boolean getEnabled() {
return enabled;
}
@Override
public String getHomeDirectory() {
return homeDirectory;
}
/**
* @param name the name to set
*/
public final void setName(String name) {
this.name = name;
}
/**
* @param password the password to set
*/
public final void setPassword(String password) {
this.password = password;
}
/**
* @param authorities the authorities to set
*/
public final void setAuthorities(List<? extends Authority> authorities) {
this.authorities = authorities;
}
/**
* @param maxIdleTime the maxIdleTime to set
*/
public final void setMaxIdleTime(int maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
/**
* @param enabled the enabled to set
*/
public final void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* @param homeDirectory the homeDirectory to set
*/
public final void setHomeDirectory(String homeDirectory) {
this.homeDirectory = homeDirectory;
}
}
/*
* Copyright 2017 Global Crop Diversity Trust, www.croptrust.org
*
* 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.apache.ftpserver.ftplet.FileSystemFactory;
import org.apache.ftpserver.ftplet.FileSystemView;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.FtpFile;
import org.apache.ftpserver.ftplet.User;
public class RepositoryFileSystemFactory implements FileSystemFactory {
@Override
public FileSystemView createFileSystemView(User user) throws FtpException {
// TODO Auto-generated method stub
return new FileSystemView() {
@Override
public boolean isRandomAccessible() throws FtpException {
// TODO Auto-generated method stub
return false;
}
@Override
public FtpFile getWorkingDirectory() throws FtpException {
// TODO Auto-generated method stub
return null;
}
@Override
public FtpFile getHomeDirectory() throws FtpException {
// TODO Auto-generated method stub
return null;
}
@Override
public FtpFile getFile(String file) throws FtpException {
// TODO Auto-generated method stub
return null;
}
@Override
public void dispose() {
// TODO Auto-generated method stub
}
@Override
public boolean changeWorkingDirectory(String dir) throws FtpException {
// TODO Auto-generated method stub
return false;
}
};
}
}
/*
* Copyright 2017 Global Crop Diversity Trust, www.croptrust.org
*
* 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 java.util.HashMap;
import java.util.Map;
import org.apache.ftpserver.ConnectionConfig;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.DefaultFtplet;
import org.apache.ftpserver.ftplet.FileSystemFactory;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.Ftplet;
import org.apache.ftpserver.ftplet.UserManager;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.message.MessageResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class RepositoryFtpServer implements InitializingBean, DisposableBean {
private final static Logger LOG = LoggerFactory.getLogger(RepositoryFtpServer.class);
@Autowired
private UserManager userManager;
@Autowired(required = false)
private MessageResource messageResource;
private int ftpPort;
private int maxThreads;
// The maximum number of simultaneous users
private int maxLogins;
// Idle timeout
private int idleTimeout;
private FtpServer server = null;
public void setUserManager(UserManager userManager) {
this.userManager = userManager;
}
public void setMessageResource(MessageResource messageResource) {
this.messageResource = messageResource;
}
public void setFtpPort(int ftpPort) {
this.ftpPort = ftpPort;
}
public void afterPropertiesSet() throws FtpException {
FtpServerFactory serverFactory = new FtpServerFactory();
serverFactory.setUserManager(userManager);
if (messageResource != null) {
serverFactory.setMessageResource(messageResource);
}
{
ListenerFactory factory = new ListenerFactory();
// set the port of the listener
factory.setPort(ftpPort);
// set idle timeout
factory.setIdleTimeout(idleTimeout);
// replace the default listener
serverFactory.addListener("default", factory.createListener());
}
FileSystemFactory fileSystem = repositoryFileSystemFactory();
serverFactory.setFileSystem(fileSystem);
final ConnectionConfig ftpConnectionConfig = new ConnectionConfig() {
@Override
public boolean isAnonymousLoginEnabled() {
return true;
}
@Override
public int getMaxThreads() {
return maxThreads;
}
@Override
public int getMaxLogins() {
return maxLogins;
}
// The number of failed login attempts before the connection is closed
@Override
public int getMaxLoginFailures() {
return 3;
}
@Override
public int getMaxAnonymousLogins() {
return 3;
}
// The number of milliseconds that the connection is delayed after a failed login attempt.
@Override
public int getLoginFailureDelay() {
return 30;
}
};
serverFactory.setConnectionConfig(ftpConnectionConfig);
Map<String, Ftplet> ftplets = new HashMap<>();
ftplets.put("default", repositoryFtplet());
serverFactory.setFtplets(ftplets);
this.server = serverFactory.createServer();
LOG.info("Starting FTP server on port {}", this.ftpPort);
server.start();
}
@Override
public void destroy() throws Exception {
if (this.server != null) {
LOG.info("Shutting down FTP server on port {}", this.ftpPort);
this.server.stop();
}
}
@Bean
public FileSystemFactory repositoryFileSystemFactory() {
return new RepositoryFileSystemFactory();
}
@Bean
public Ftplet repositoryFtplet() {
return new DefaultFtplet() {
};
}
}
/*
* Copyright 2017 Global Crop Diversity Trust, www.croptrust.org
*
* 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 java.util.ArrayList;
import java.util.List;
import org.apache.ftpserver.ftplet.Authentication;
import org.apache.ftpserver.ftplet.AuthenticationFailedException;
import org.apache.ftpserver.ftplet.Authority;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.User;
import org.apache.ftpserver.ftplet.UserManager;
import org.apache.ftpserver.usermanager.AnonymousAuthentication;
import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication;
import org.apache.ftpserver.usermanager.impl.AbstractUserManager;
import org.apache.ftpserver.usermanager.impl.ConcurrentLoginPermission;
import org.apache.ftpserver.usermanager.impl.WritePermission;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig {
@Bean
public RepositoryFtpServer ftpServer() {
RepositoryFtpServer ftpServer = new RepositoryFtpServer();
ftpServer.setFtpPort(8021);
ftpServer.setUserManager(userManager());
return ftpServer;
}
@Bean
public UserManager userManager() {
return new AbstractUserManager() {
@Override
public void save(User user) throws FtpException {
// Noop
}
@Override
public User getUserByName(String username) throws FtpException {
FtpUser user = new FtpUser(username);
user.setPassword(username + "1!");
user.setEnabled(true);
List<Authority> authorities = new ArrayList<>();
authorities.add(new ConcurrentLoginPermission(2, 0));
authorities.add(new WritePermission());
user.setAuthorities(authorities);
return user;
}
@Override
public String[] getAllUserNames() throws FtpException {
return new String[] {};
}
@Override
public boolean doesExist(String username) throws FtpException {
// TODO Auto-generated method stub
return true;
}
@Override
public void delete(String username) throws FtpException {
// Noop
}
@Override
public User authenticate(Authentication authentication) throws AuthenticationFailedException {
if (authentication instanceof UsernamePasswordAuthentication) {
UsernamePasswordAuthentication upauth = (UsernamePasswordAuthentication) authentication;
String user = upauth.getUsername();
String password = upauth.getPassword();
if (user == null) {
throw new AuthenticationFailedException("Authentication failed");
}
if (password == null) {
password = "";
}
String storedPassword = user.concat("1!");
if (!password.equals(storedPassword)) {
// user does not exist
throw new AuthenticationFailedException("Authentication failed");
}
// if (getPasswordEncryptor().matches(password, storedPassword)) {
if (password.equals(storedPassword)) {
try {
return getUserByName(user);
} catch (FtpException e) {
throw new AuthenticationFailedException("Authentication failed", e);
}
} else {
throw new AuthenticationFailedException("Authentication failed");
}
} else if (authentication instanceof AnonymousAuthentication) {
throw new AuthenticationFailedException("Authentication failed");
} else {
throw new IllegalArgumentException("Authentication not supported by this user manager");
}
}
};
}
}
/*
* Copyright 2017 Global Crop Diversity Trust, www.croptrust.org
*
* 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 static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import java.net.SocketException;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { ApplicationConfig.class })
public class FtpServerTest {
private String username = "user";
private String password = username + "1!";
@Test
public void serverListening() throws SocketException, IOException {
final FTPClient ftp = new FTPClient();
try {
ftp.connect("localhost", 8021);
assertThat("FTP server refused connection", FTPReply.isPositiveCompletion(ftp.getReplyCode()), is(true));
} finally {
ftp.disconnect();
}
}