Dave Ward f059a12979 Merged V4.1-BUG-FIX to HEAD
40164: Merged V4.0.2 to V4.1-BUG-FIX (4.1.1)
      - Partial merge of r39519 on V4.1 which included something from V4.1-BUG-FIX
      39494: ALF-15170: Can't change folder permissions in Private or Public-moderated sites
      - Fix by Dmitry V
      38899: ALF-15005: Merged V4.0-BUG-FIX to PATCHES/V4.0.2
         37920: ALF-13816: Permission Denied on web-client browsing if parent does not inherit permissions
            - FileFolderService getNamePath() now performs toFileInfo() as SystemUser.
   40167: Merged V4.1 (4.1.0) to V4.1-BUG-FIX (4.1.1)
      40156: Adds defensive code to avoid situations where the parent can't be found (e.g. RM-474)
      40144: ALF-11027: update URLs to deploy to our internal-snapshot Maven repo, so that GoogleDocs integration can take advantage of it
      40121: Chinese: Translation update based on EN r40086
      40120: Dutch: Translation update based on EN r40086
      40119: Japanese: Translation update based on EN r40086
      40118: Italian: Translation update based on EN r40086
      40117: French: Translation update based on EN r40086
      40116: Spanish: Translation update based on EN r40086
      40115: German: Translation update based on EN r40086
      40103: Unit test fix following removal of AVM file context.
      40076: Fixes encoding error introduced in r40072.
      40072: Error message string updates, based on feedback from Docs.
      40069: ALF-15364	CloudSync: folder sync with lock on-premise - pulled new file is not auto-locked
      40068: Java doc corrections.
      40067: CloudSync: ALF-15287 - folder sync to a clashing name hierarchy causes file to be indirectly synced to the wrong place
      - was "An unsynced file which was synced indirectly will be synced in the documentLibrary"
      - plus unit test
      40065: Removal of potential NullPointerException (not observed).
      40063: Chinese: Translation updates from Gloria, based on EN r40019
      40062: Dutch: Translation updates from Gloria, based on EN r40019
      40061: Italian: Translation updates from Gloria, based on EN r40019
      40060: French: Translation updates from Gloria, based on EN r40019
      40059: Japanese: Translation updates from Gloria, based on EN r40019
      40058: Spanish: Translation updates from Gloria, based on EN r40019
      40057: German: Translation updates from Gloria, based on EN r40019
      40054: CloudSync: add simple *folder* sync unit test !
      40053: ALF-15218 - AVM must not be visible by default in VFS mount points
      40036: Addressing the important question raised in ALF-14950.
      I have decided that, as pointed out by MikeF, a sensible solution for handling secondary child-assocs that link nodes to synced folders would be to NOT sync those nodes.
      So I've made it such that new nodes are only added into a syncset if they are created as *primary* children of a synced folder. If they are secondary children, they are not added as members.
      Test case for same.
      40019: Having fixed the TemporaryNodes @Rule in rev 40010 so that it handles checked-out test files gracefully, I'm removing Jan's workaround for a checked-out test file.
      40017: During work for ALF-15296, I've fixed up the error handling during sync set creation.
      See ts doc: https://ts.alfresco.com/share/page/site/projectodin/document-details?nodeRef=workspace://SpacesStore/47bf11a3-8d30-4377-a373-1c1d3fba6018
      Now Sync Set creation can fail if there are some problems with the provided direct member nodes.
      If there are problems with any indirect members, that will not cause the sync to fail during setup, but subsequent pushes to the Cloud will fail with appropriate error reporting.
      Exceptions during the creation of a syncset can now report multiple failed nodes.
      Rewrote the cannotCreateTwoSyncSetDefinitionsWithOverlappingLocalMembers() testcase to be creatingTwoSyncSetDefinitionsWithOverlappingLocalMembersShouldSilentlyDropTheOverlaps()
      Extra test cases for sync setup:
      syncing a list of ineligible nodes should throw an exception.
      Syncing a list of nodes, some of which are ineligible, should skip the ineligable ones.
      Syncing a folder containing already-synced child nodes and unwriteable child nodes should silently
      skip the already-synced, but include the unwriteable nodes as members (which will fail to sync).
      Various tidy-ups and comments to explain this behaviour to the next coder, which may be me. :)
      40014: ALF-15218 - AVM must not be visible by default in VFS mount points
      40010: Enhancement to TemporaryNodes JUnit @Rule so that it gracefully handles checked-out nodes during test code.
      39996: ALF-14377: Need to decide what to do when SSD creator user is no longer valid
      39986: Chinese: Translation updates from Gloria, based on EN r39671
      39985: Dutch: Translation updates from Gloria, based on EN r39671
      39984: Japanese: Translation updates from Gloria, based on EN r39671
      39983: Italian: Translation updates from Gloria, based on EN r39671
      39982: French: Translation updates from Gloria, based on EN r39671
      39981: Spanish: Translation updates from Gloria, based on EN r39671
      39980: German: Translation updates from Gloria, based on EN r39671
      39957: CloudSync: minor - some info logging (applies to src + tgt)
      39943: CloudSync: fix remainder of ALF-15130 - sync time for initial sync of working copy (+ unit test)
      39942: ALF-14911 - Path shown in the version comments does not match the path shown in the sync status dialogue
         - first part share paths are now handled separatly
      39933: ALF-14179: Merged PATCHES/V4.0.2 to V4.1
         39929: ALF-15330: Merged V3.4-BUG-FIX to PATCHES/V4.0.2
            39681: Fix CIFS hang on file open of MS Office document. ALF-13578, ALF-15092.
               Async packet queue not being cleared at end of request processing.
      39924: CSS fixes for the sync panel
      39918: Merged DEV to V4.1
         39911: ALF-15263 : XAM: XAM Connector cannot be installed on Alfresco v4.1
      39915: ALF-14908: Update Sync status dialogue to include the local root folder for indirectly synced nodes
      39904: ALF-15041: Cloud Sync doesn't work with IE7
      39903: ALF-15041: Cloud Sync doesn't work with IE7
      39902: ALF-15041: Cloud Sync doesn't work with IE7
      39883: CloudSync: fix ALF-15270 - moving directly synced node (on source) causes target node to be deleted
      - ... add related move file sync unit test
      39876: ALF-15301: IE8 specific: It is impossible creating a new folder in a cloud target selection window/Cloud location
      39864: ALF-15003 Sync failed for the file (where write access on source removed for sync owner) but no matter this file appears in Cloud
      39863: ALF-15268: Cancel button disabled in the target selection window for new folder creation in Cloud
      39860: ALF-14908: Update Sync status dialogue to include the local root folder for indirectly synced nodes.
      39859: ALF-15249 Add a new method hasSyncSetDefintions, with unit test, and use this to skip the Sync Pull step on-premise if no SSDs have been defined on the local system
      39847: ALF-15185: YUI Overlay Mozilla Scrollbar fix should be prevented in recent versions of Firefox too.
      39826: ALF-14377 - Need to decide what to do when SSD creator user is no longer valid
      Implementation of "last gasp" unsync for the case where the sync set owner does not exist.
      39820: ALF-15267: Edit online is available for locked on-premise copy 
      and can unlock it
      ALF-15284: The actions "Upload New Version" and "Inline Edit" should not be available for a "synced" and "locked" node
      39812: UI Bug Fixes:
         - ALF-15174: Uses already copied slingshot.properties file for message strings needed in both repo and Share
         - Cloud delete file message shouldn't refer to the cloud
      39810: ALF-15221: Cloud Sync Status: Failure message when getting location is misleading
      39767: CloudSync: fix ALF-15145 - Unsync of a folder (sync) that has a working copy file causes indirect icon to remain ...
      ... on working copy (until checkin or cancel checkout)
      39748: CloudSync: fix compile error
      - failed manual "merge" (oops, sorry)
      39747: ALF-14377	Need to decide what to do when SSD creator user is no longer valid 
         - part 1 nodes are marked.
      39735: CloudSync: ALF-14483 - multi-threaded SyncTracker
      - fix related to unit test failure (affecting CLOUD1 but not V4.1)
      - also add default (info) Sync log level
      39729: Merged PATCHES/V4.0.2 to V4.1
         39725: ALF-15176: Multi-threaded SOLR tracking suddenly stops leaving transactions unindexed
            - Debugged on environment provided by Antonio
            - If the last incomplete batch in a chunk consists entirely of empty transactions (or transactions consisting entirely of nodes updated in later transactions) it would loop forever
      39687: Enterprise license for enterprise remote api project
      39686: Replaced GPL license with enterprise licence.
      39683: ALF-15162 - Unclear error message when delta syncing and the target is gone
      39677: ALF-14903 Move the Sync allowed/not-allowed check, based on Network/Tenant, to AbstractCloudSyncAbstractWebScript, and have this used by the webscripts based on this too. Then, allow UnSync to proceed no matter what the tenant status, and add unit tests
      39673: New unit test for adding checked out nodes into a sync set.
      39670: Rework to cloud key license stuff - introduces a ValidLicenseEvent
      39664: CloudSync: ALF-14483 - SyncTracker unit tests - simple file sync test
      - initially run with single-threaded push/pull (will require further investigation on cloud branch)
      39653: ALF-15041: Cloud Sync doesn't work with IE7
      39643: ALF-15220: Indirect sync and sync failed indicators appeared together on nodes during the demo
      39641: ALF-15230 Refactor the lazy container creation code, to use the new SystemNodeUtils helper
      39640: ALF-15231 Restore, with refactorings / making more general, the ability to lazy create the remote credentials container if bootstrap has not created it (eg on Cloud)
      39639: ALF-15238: Bitrock Installer: Readme.txt should be updated - Alfresco version is 4.0
      39633: CloudSync: ALF-14483 - SyncTracker - enable sync sets to be processed in parallel
      - wip default of 6 threads (3 to push / 3 to pull)
      39616: CloudSync: CloudSync: ALF-14483 - SyncTracker - multiple syncs (to different tgt folders) with multiple files
      - make sure the multi-threaded tracker is excercised via automated unit tests
      39613: Refactor of test code. SyncTrackerComponentTest now uses JUnit Rules to manage temporary content and to undo mocking out of services.
      39607: CloudSync: ALF-14483 - SyncTracker - enable sync sets to be pushed in parallel
      - wip / default 3 threads
      39606: CloudSync: minor log level adjustments
      - eg. info vs debug (vs trace)
      39598: CloudSync: ALF-14655 - update simple file sync (to test multiple sync'd files)
      39597: CloudSync: tweak a couple of unit tests
      - add "run id" to allow them to be locally re-runnable (in case of error)
      39589: ALF-15098: Folder Picker causes JS error in the document details page
      39587: ALF-15222: Cloud Folder picker: Newly created folder isn't selected
      39580: CloudSync: fix Unsync - to ensure we don't leave sync aspects (such as failed, which reappears if src file is re-synced)
      - found by MF ... thanks
      - unit test added
      39574: Cloud lacks a guest mode, so switch to unauthenticated for the sync mode fetch (user credentials may not be available)
      39566: Allow the sync mode to be fetched by guest (it need not be hidden), then fetch from Share as guest, to avoid problems when fetching the sync mode if the current user's credentials have expired (otherwise it breaks the redirect back to the login)
      39561: Merged THOR1 to V4.1 (try to say that in a hurry!)
         39553: ALF-15149: Prevent an infinite loop in AlfrescoSolrEventListener.RemoveNullEntriesCacheMatch.updateCache()
      39550: Synced folder shouldn't be filtered in the cloud folder picker. The user should be able to see them but not select them (the synced folder should be greyed out)
      39548: CloudSync: ALF-14655 - add SyncTracker unit test for simple file sync
      - initial re-work of SyncTrackerComponentTest ... to make it easier to add more tests (and re-use test fragments)
      - add simple unit test for "file sync" (F4) => sync, push, pull, unsync ...
      - ... also fix potential SyncService unsync issue on target (found by new test)
      39547: Bug fixes (spotted during demo):
       - Remove the "synced with errors" filter from dashlets and doclibrary for the cloud version
       - Remove the last '>' symbol in the info dialogue (path for "Cloud in the Location")
      39546: Merged integrations/GoogleDocs/BRANCHES/OAUTH_V4.1 to BRANCHES/V4.1:
         39504: [ALF-14926][GOOGLEDOCS-23] Merge OAuth2 Credentials Store Service
         39516: Add missing license to OAuth2 Credentials Classes
         39517: oAuth1 Credentials Store Service
      39540: Some fixes to issues identified by findbugs.
      39532: Revert cloud welcome dashlet text.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@40273 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2012-08-10 08:19:54 +00:00

751 lines
27 KiB
Java

/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.filesys;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import junit.framework.TestCase;
import org.alfresco.jlan.ftp.FTPConfigSection;
import org.alfresco.jlan.server.config.ServerConfigurationAccessor;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.usage.ContentUsageImpl;
import org.alfresco.repo.usage.UserUsageTrackingComponent;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.PropertyMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.context.ApplicationContext;
/**
* End to end JUNIT test of the FTP server
*
* Uses the commons-net ftp client library to connect to the
* Alfresco FTP server.
*/
public class FTPServerTest extends TestCase
{
private static Log logger = LogFactory.getLog(FTPServerTest.class);
private ApplicationContext applicationContext;
private final String USER_ADMIN="admin";
private final String PASSWORD_ADMIN="admin";
private final String USER_ONE = "FTPServerTestOne";
private final String USER_TWO = "FTPServerTestTwo";
private final String USER_THREE = "FTPServerTestThree";
private final String PASSWORD_ONE="Password01";
private final String PASSWORD_TWO="Password02";
private final String PASSWORD_THREE="Password03";
private final String HOSTNAME="localhost";
private NodeService nodeService;
private PersonService personService;
private MutableAuthenticationService authenticationService;
private AuthenticationComponent authenticationComponent;
private TransactionService transactionService;
private Repository repositoryHelper;
private PermissionService permissionService;
private FTPConfigSection ftpConfigSection;
@Override
protected void setUp() throws Exception
{
applicationContext = ApplicationContextHelper.getApplicationContext();
nodeService = (NodeService)applicationContext.getBean("nodeService");
personService = (PersonService)applicationContext.getBean("personService");
authenticationService = (MutableAuthenticationService)applicationContext.getBean("AuthenticationService");
authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent");
transactionService = (TransactionService)applicationContext.getBean("transactionService");
repositoryHelper = (Repository)applicationContext.getBean("repositoryHelper");
permissionService = (PermissionService)applicationContext.getBean("permissionService");
ServerConfigurationAccessor fileServerConfiguration = (ServerConfigurationAccessor)applicationContext.getBean("fileServerConfiguration");
ftpConfigSection = (FTPConfigSection) fileServerConfiguration.getConfigSection( FTPConfigSection.SectionName);
assertNotNull("nodeService is null", nodeService);
assertNotNull("reporitoryHelper is null", repositoryHelper);
assertNotNull("personService is null", personService);
assertNotNull("authenticationService is null", authenticationService);
assertNotNull("authenticationComponent is null", authenticationComponent);
authenticationComponent.setSystemUserAsCurrentUser();
final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper();
RetryingTransactionCallback<Void> createUsersCB = new RetryingTransactionCallback<Void>() {
@Override
public Void execute() throws Throwable
{
createUser(USER_ONE, PASSWORD_ONE, -1);
createUser(USER_TWO, PASSWORD_TWO, -1);
createUser(USER_THREE, PASSWORD_THREE, 30);
return null;
}
};
tran.doInTransaction(createUsersCB);
RetryingTransactionCallback<Void> createTestDirCB = new RetryingTransactionCallback<Void>() {
@Override
public Void execute() throws Throwable
{
{
NodeRef userOneHome = repositoryHelper.getUserHome(personService.getPerson(USER_ONE));
permissionService.setPermission(userOneHome, USER_TWO, PermissionService.CONTRIBUTOR, true);
permissionService.setPermission(userOneHome, USER_TWO, PermissionService.WRITE, true);
}
return null;
}
};
tran.doInTransaction(createTestDirCB, false, true);
}
protected void tearDown() throws Exception
{
// UserTransaction txn = transactionService.getUserTransaction();
// assertNotNull("transaction leaked", txn);
// txn.getStatus();
// txn.rollback();
}
/**
* Simple test that connects to the inbuilt ftp server and logs on
*
* @throws Exception
*/
public void testFTPConnect() throws Exception
{
logger.debug("Start testFTPConnect");
FTPClient ftp = connectClient();
try
{
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
fail("FTP server refused connection.");
}
boolean login = ftp.login(USER_ADMIN, PASSWORD_ADMIN);
assertTrue("admin login not successful", login);
}
finally
{
ftp.disconnect();
}
}
/**
* Simple negative test that connects to the inbuilt ftp server and attempts to
* log on with the wrong password.
*
* @throws Exception
*/
public void testFTPConnectNegative() throws Exception
{
logger.debug("Start testFTPConnectNegative");
FTPClient ftp = connectClient();
try
{
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
fail("FTP server refused connection.");
}
boolean login = ftp.login(USER_ADMIN, "garbage");
assertFalse("admin login successful", login);
// now attempt to list the files and check that the command does not
// succeed
FTPFile[] files = ftp.listFiles();
assertNotNull(files);
assertTrue(files.length == 0);
reply = ftp.getReplyCode();
assertTrue(FTPReply.isNegativePermanent(reply));
}
finally
{
ftp.disconnect();
}
}
/**
* Test CWD for FTP server
*
* @throws Exception
*/
public void testCWD() throws Exception
{
logger.debug("Start testCWD");
FTPClient ftp = connectClient();
try
{
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
fail("FTP server refused connection.");
}
boolean login = ftp.login(USER_ADMIN, PASSWORD_ADMIN);
assertTrue("admin login successful", login);
FTPFile[] files = ftp.listFiles();
reply = ftp.getReplyCode();
assertTrue(FTPReply.isPositiveCompletion(reply));
// expect /Alfresco directory
// /AVM directory - avm removed
assertTrue(files.length == 1);
boolean foundAVM=false;
boolean foundAlfresco=false;
for(FTPFile file : files)
{
logger.debug("file name=" + file.getName());
assertTrue(file.isDirectory());
if(file.getName().equalsIgnoreCase("AVM"))
{
foundAVM=true;
}
if(file.getName().equalsIgnoreCase("Alfresco"))
{
foundAlfresco=true;
}
}
// AVM mount point removed
//assertTrue(foundAVM);
assertTrue(foundAlfresco);
// Change to Alfresco Dir that we know exists
reply = ftp.cwd("/Alfresco");
assertTrue(FTPReply.isPositiveCompletion(reply));
// relative path with space char
reply = ftp.cwd("Data Dictionary");
assertTrue(FTPReply.isPositiveCompletion(reply));
// non existant absolute
reply = ftp.cwd("/Garbage");
assertTrue(FTPReply.isNegativePermanent(reply));
reply = ftp.cwd("/Alfresco/User Homes");
assertTrue(FTPReply.isPositiveCompletion(reply));
// Wild card
reply = ftp.cwd("/Alfresco/User*Homes");
assertTrue("unable to change to /Alfresco User*Homes/", FTPReply.isPositiveCompletion(reply));
// // Single char pattern match
// reply = ftp.cwd("/Alfre?co");
// assertTrue("Unable to match single char /Alfre?co", FTPReply.isPositiveCompletion(reply));
// two level folder
reply = ftp.cwd("/Alfresco/Data Dictionary");
assertTrue("unable to change to /Alfresco/Data Dictionary", FTPReply.isPositiveCompletion(reply));
// go up one
reply = ftp.cwd("..");
assertTrue("unable to change to ..", FTPReply.isPositiveCompletion(reply));
reply = ftp.pwd();
ftp.getStatus();
assertTrue("unable to get status", FTPReply.isPositiveCompletion(reply));
// check we are at the correct point in the tree
reply = ftp.cwd("Data Dictionary");
assertTrue(FTPReply.isPositiveCompletion(reply));
}
finally
{
ftp.disconnect();
}
}
/**
* Test CRUD for FTP server
*
* @throws Exception
*/
public void testCRUD() throws Exception
{
final String PATH1 = "FTPServerTest";
final String PATH2 = "Second part";
logger.debug("Start testFTPCRUD");
FTPClient ftp = connectClient();
try
{
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
fail("FTP server refused connection.");
}
boolean login = ftp.login(USER_ADMIN, PASSWORD_ADMIN);
assertTrue("admin login successful", login);
reply = ftp.cwd("/Alfresco/User Homes");
assertTrue(FTPReply.isPositiveCompletion(reply));
// Delete the root directory in case it was left over from a previous test run
try
{
ftp.removeDirectory(PATH1);
}
catch (IOException e)
{
// ignore this error
}
// make root directory
ftp.makeDirectory(PATH1);
ftp.cwd(PATH1);
// make sub-directory in new directory
ftp.makeDirectory(PATH2);
ftp.cwd(PATH2);
// List the files in the new directory
FTPFile[] files = ftp.listFiles();
assertTrue("files not empty", files.length == 0);
// Create a file
String FILE1_CONTENT_1="test file 1 content";
String FILE1_NAME = "testFile1.txt";
ftp.appendFile(FILE1_NAME , new ByteArrayInputStream(FILE1_CONTENT_1.getBytes("UTF-8")));
// Get the new file
FTPFile[] files2 = ftp.listFiles();
assertTrue("files not one", files2.length == 1);
InputStream is = ftp.retrieveFileStream(FILE1_NAME);
String content = inputStreamToString(is);
assertEquals("Content is not as expected", content, FILE1_CONTENT_1);
ftp.completePendingCommand();
// Update the file contents
String FILE1_CONTENT_2="That's how it is says Pooh!";
ftp.appendFile(FILE1_NAME , new ByteArrayInputStream(FILE1_CONTENT_2.getBytes("UTF-8")));
InputStream is2 = ftp.retrieveFileStream(FILE1_NAME);
String content2 = inputStreamToString(is2);
assertEquals("Content is not as expected", FILE1_CONTENT_2, content2);
ftp.completePendingCommand();
// now delete the file we have been using.
assertTrue (ftp.deleteFile(FILE1_NAME));
// negative test - file should have gone now.
assertFalse (ftp.deleteFile(FILE1_NAME));
}
finally
{
// clean up tree if left over from previous run
ftp.disconnect();
}
}
/**
* Test of obscure path names in the FTP server
*
* RFC959 states that paths are constructed thus...
* <string> ::= <char> | <char><string>
* <pathname> ::= <string>
* <char> ::= any of the 128 ASCII characters except <CR> and <LF>
*
* So we need to check how high characters and problematic are encoded
*/
public void testPathNames() throws Exception
{
logger.debug("Start testPathNames");
FTPClient ftp = connectClient();
String PATH1="testPathNames";
try
{
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
fail("FTP server refused connection.");
}
boolean login = ftp.login(USER_ADMIN, PASSWORD_ADMIN);
assertTrue("admin login successful", login);
reply = ftp.cwd("/Alfresco/User*Homes");
assertTrue(FTPReply.isPositiveCompletion(reply));
// Delete the root directory in case it was left over from a previous test run
try
{
ftp.removeDirectory(PATH1);
}
catch (IOException e)
{
// ignore this error
}
// make root directory for this test
boolean success = ftp.makeDirectory(PATH1);
assertTrue("unable to make directory:" + PATH1, success);
success = ftp.changeWorkingDirectory(PATH1);
assertTrue("unable to change to working directory:" + PATH1, success);
assertTrue("with a space", ftp.makeDirectory("test space"));
assertTrue("with exclamation", ftp.makeDirectory("space!"));
assertTrue("with dollar", ftp.makeDirectory("space$"));
assertTrue("with brackets", ftp.makeDirectory("space()"));
assertTrue("with hash curley brackets", ftp.makeDirectory("space{}"));
//Pound sign U+00A3
//Yen Sign U+00A5
//Capital Omega U+03A9
assertTrue("with pound sign", ftp.makeDirectory("pound \u00A3.world"));
assertTrue("with yen sign", ftp.makeDirectory("yen \u00A5.world"));
// Test steps that do not work
// assertTrue("with omega", ftp.makeDirectory("omega \u03A9.world"));
// assertTrue("with obscure ASCII chars", ftp.makeDirectory("?/.,<>"));
}
finally
{
// clean up tree if left over from previous run
ftp.disconnect();
}
}
/**
* Create a user other than "admin" who has access to a set of files.
*
* Create a folder containing test.docx as user one
* Update that file as user two.
* Check user one can see user two's changes.
*
* @throws Exception
*/
public void testTwoUserUpdate() throws Exception
{
logger.debug("Start testFTPConnect");
final String TEST_DIR="/Alfresco/User Homes/" + USER_ONE;
final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper();
FTPClient ftpOne = connectClient();
FTPClient ftpTwo = connectClient();
try
{
int reply = ftpOne.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
fail("FTP server refused connection.");
}
reply = ftpTwo.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
fail("FTP server refused connection.");
}
boolean login = ftpOne.login(USER_ONE, PASSWORD_ONE);
assertTrue("user one login not successful", login);
login = ftpTwo.login(USER_TWO, PASSWORD_TWO);
assertTrue("user two login not successful", login);
boolean success = ftpOne.changeWorkingDirectory("Alfresco");
assertTrue("user one unable to cd to Alfreco", success);
success = ftpOne.changeWorkingDirectory("User*Homes");
assertTrue("user one unable to cd to User*Homes", success);
success = ftpOne.changeWorkingDirectory(USER_ONE);
assertTrue("user one unable to cd to " + USER_ONE, success);
success = ftpTwo.changeWorkingDirectory("Alfresco");
assertTrue("user two unable to cd to Alfreco", success);
success = ftpTwo.changeWorkingDirectory("User*Homes");
assertTrue("user two unable to cd to User*Homes", success);
success = ftpTwo.changeWorkingDirectory(USER_ONE);
assertTrue("user two unable to cd " + USER_ONE, success);
// Create a file as user one
String FILE1_CONTENT_1="test file 1 content";
String FILE1_NAME = "test.docx";
success = ftpOne.appendFile(FILE1_NAME , new ByteArrayInputStream(FILE1_CONTENT_1.getBytes("UTF-8")));
assertTrue("user one unable to append file", success);
// Update the file as user two
String FILE1_CONTENT_2="test file content updated";
success = ftpTwo.appendFile(FILE1_NAME , new ByteArrayInputStream(FILE1_CONTENT_2.getBytes("UTF-8")));
assertTrue("user two unable to append file", success);
// User one should read user2's content
InputStream is1 = ftpOne.retrieveFileStream(FILE1_NAME);
assertNotNull("is1 is null", is1);
String content1 = inputStreamToString(is1);
assertEquals("Content is not as expected", FILE1_CONTENT_2, content1);
ftpOne.completePendingCommand();
// User two should read user2's content
InputStream is2 = ftpTwo.retrieveFileStream(FILE1_NAME);
assertNotNull("is2 is null", is2);
String content2 = inputStreamToString(is2);
assertEquals("Content is not as expected", FILE1_CONTENT_2, content2);
ftpTwo.completePendingCommand();
logger.debug("Test finished");
}
finally
{
ftpOne.dele(TEST_DIR);
if(ftpOne != null)
{
ftpOne.disconnect();
}
if(ftpTwo != null)
{
ftpTwo.disconnect();
}
}
}
/**
* Test a quota failue exception over FTP.
* A file should not exist after a create and quota exception.
*/
public void testFtpQuotaAndFtp() throws Exception
{
// Enable usages
ContentUsageImpl contentUsage = (ContentUsageImpl)applicationContext.getBean("contentUsageImpl");
contentUsage.setEnabled(true);
contentUsage.init();
UserUsageTrackingComponent userUsageTrackingComponent = (UserUsageTrackingComponent)applicationContext.getBean("userUsageTrackingComponent");
userUsageTrackingComponent.setEnabled(true);
userUsageTrackingComponent.bootstrapInternal();
final String TEST_DIR="/Alfresco/User Homes/" + USER_THREE;
final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper();
FTPClient ftpOne = connectClient();
try
{
int reply = ftpOne.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
fail("FTP server refused connection.");
}
boolean login = ftpOne.login(USER_THREE, PASSWORD_THREE);
assertTrue("user one login not successful", login);
boolean success = ftpOne.changeWorkingDirectory("Alfresco");
assertTrue("user one unable to cd to Alfreco", success);
success = ftpOne.changeWorkingDirectory("User*Homes");
assertTrue("user one unable to cd to User*Homes", success);
success = ftpOne.changeWorkingDirectory(USER_THREE);
assertTrue("user one unable to cd to " + USER_THREE, success);
/**
* Create a file as user three which is bigger than the quota
*/
String FILE3_CONTENT_3="test file 3 content that needs to be greater than 100 bytes to result in a quota exception being thrown";
String FILE1_NAME = "test.docx";
// Should not be success
success = ftpOne.appendFile(FILE1_NAME , new ByteArrayInputStream(FILE3_CONTENT_3.getBytes("UTF-8")));
assertFalse("user one can ignore quota", success);
boolean deleted = ftpOne.deleteFile(FILE1_NAME);
assertFalse("quota exception expected", deleted);
logger.debug("test done");
}
finally
{
// Disable usages
contentUsage.setEnabled(false);
contentUsage.init();
userUsageTrackingComponent.setEnabled(false);
userUsageTrackingComponent.bootstrapInternal();
ftpOne.dele(TEST_DIR);
if(ftpOne != null)
{
ftpOne.disconnect();
}
}
}
/**
* Create a user with a small quota.
*
* Upload a file less than the quota.
*
* Upload a file greater than the quota.
*
* @throws Exception
*/
public void DISABLED_testQuota() throws Exception
{
fail("not yet implemented");
}
private FTPClient connectClient() throws IOException
{
FTPClient ftp = new FTPClient();
if(logger.isDebugEnabled())
{
ftp.addProtocolCommandListener(new PrintCommandListener(
new PrintWriter(System.out)));
}
ftp.connect(HOSTNAME, ftpConfigSection.getFTPPort());
return ftp;
}
/**
* Test quality utility to read an input stream into a string.
* @param is
* @return the content of the stream in a string.
* @throws IOException
*/
private String inputStreamToString(InputStream is) throws IOException
{
if (is != null)
{
StringWriter writer = new StringWriter();
char[] buffer = new char[1024];
try
{
Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
int n;
while ((n = reader.read(buffer)) != -1)
{
writer.write(buffer, 0, n);
}
}
finally
{
is.close();
}
is.close();
return writer.getBuffer().toString();
}
return "";
}
/**
* create a test user
* @param userName
* @param password
* @param quota
*/
private void createUser(String userName, String password, long quota)
{
if (this.authenticationService.authenticationExists(userName) == false)
{
this.authenticationService.createAuthentication(userName, password.toCharArray());
PropertyMap ppOne = new PropertyMap(4);
ppOne.put(ContentModel.PROP_USERNAME, userName);
ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName");
ppOne.put(ContentModel.PROP_LASTNAME, "lastName");
ppOne.put(ContentModel.PROP_EMAIL, "email@email.com");
ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle");
if(quota > 0)
{
ppOne.put(ContentModel.PROP_SIZE_QUOTA, quota);
}
this.personService.createPerson(ppOne);
}
}
}