diff --git a/config/alfresco/messages/patch-service_de.properties b/config/alfresco/messages/patch-service_de.properties index 4bcb96b00a..fbd1ffe5bd 100755 --- a/config/alfresco/messages/patch-service_de.properties +++ b/config/alfresco/messages/patch-service_de.properties @@ -425,6 +425,8 @@ patch.activitiesTemplatesUpdate.description=Updates activities email templates. patch.activitiesTemplatesUpdate.error=Error retrieving base template when trying to patch the activity email templates. patch.activitiesTemplatesUpdate.result=Updated activities email templates. +patch.activitiesEmailTemplate.description=Creates activities email templates. + patch.avmToAdmRemoteStore.description=Migrates Share Surf config from AVM sitestore to DM Sites folder. patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration. diff --git a/config/alfresco/messages/patch-service_es.properties b/config/alfresco/messages/patch-service_es.properties index 4bcb96b00a..fbd1ffe5bd 100755 --- a/config/alfresco/messages/patch-service_es.properties +++ b/config/alfresco/messages/patch-service_es.properties @@ -425,6 +425,8 @@ patch.activitiesTemplatesUpdate.description=Updates activities email templates. patch.activitiesTemplatesUpdate.error=Error retrieving base template when trying to patch the activity email templates. patch.activitiesTemplatesUpdate.result=Updated activities email templates. +patch.activitiesEmailTemplate.description=Creates activities email templates. + patch.avmToAdmRemoteStore.description=Migrates Share Surf config from AVM sitestore to DM Sites folder. patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration. diff --git a/config/alfresco/messages/patch-service_fr.properties b/config/alfresco/messages/patch-service_fr.properties index 4bcb96b00a..fbd1ffe5bd 100755 --- a/config/alfresco/messages/patch-service_fr.properties +++ b/config/alfresco/messages/patch-service_fr.properties @@ -425,6 +425,8 @@ patch.activitiesTemplatesUpdate.description=Updates activities email templates. patch.activitiesTemplatesUpdate.error=Error retrieving base template when trying to patch the activity email templates. patch.activitiesTemplatesUpdate.result=Updated activities email templates. +patch.activitiesEmailTemplate.description=Creates activities email templates. + patch.avmToAdmRemoteStore.description=Migrates Share Surf config from AVM sitestore to DM Sites folder. patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration. diff --git a/config/alfresco/messages/patch-service_it.properties b/config/alfresco/messages/patch-service_it.properties index 4bcb96b00a..fbd1ffe5bd 100755 --- a/config/alfresco/messages/patch-service_it.properties +++ b/config/alfresco/messages/patch-service_it.properties @@ -425,6 +425,8 @@ patch.activitiesTemplatesUpdate.description=Updates activities email templates. patch.activitiesTemplatesUpdate.error=Error retrieving base template when trying to patch the activity email templates. patch.activitiesTemplatesUpdate.result=Updated activities email templates. +patch.activitiesEmailTemplate.description=Creates activities email templates. + patch.avmToAdmRemoteStore.description=Migrates Share Surf config from AVM sitestore to DM Sites folder. patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration. diff --git a/config/alfresco/messages/patch-service_ja.properties b/config/alfresco/messages/patch-service_ja.properties index 4bcb96b00a..fbd1ffe5bd 100755 --- a/config/alfresco/messages/patch-service_ja.properties +++ b/config/alfresco/messages/patch-service_ja.properties @@ -425,6 +425,8 @@ patch.activitiesTemplatesUpdate.description=Updates activities email templates. patch.activitiesTemplatesUpdate.error=Error retrieving base template when trying to patch the activity email templates. patch.activitiesTemplatesUpdate.result=Updated activities email templates. +patch.activitiesEmailTemplate.description=Creates activities email templates. + patch.avmToAdmRemoteStore.description=Migrates Share Surf config from AVM sitestore to DM Sites folder. patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration. diff --git a/config/alfresco/messages/patch-service_nl.properties b/config/alfresco/messages/patch-service_nl.properties index 4bcb96b00a..fbd1ffe5bd 100755 --- a/config/alfresco/messages/patch-service_nl.properties +++ b/config/alfresco/messages/patch-service_nl.properties @@ -425,6 +425,8 @@ patch.activitiesTemplatesUpdate.description=Updates activities email templates. patch.activitiesTemplatesUpdate.error=Error retrieving base template when trying to patch the activity email templates. patch.activitiesTemplatesUpdate.result=Updated activities email templates. +patch.activitiesEmailTemplate.description=Creates activities email templates. + patch.avmToAdmRemoteStore.description=Migrates Share Surf config from AVM sitestore to DM Sites folder. patch.avmToAdmRemoteStore.complete=Completed Share Surf config migration. diff --git a/config/alfresco/messages/templates-messages_ja.properties b/config/alfresco/messages/templates-messages_ja.properties index e39202984d..8efd4075b8 100755 --- a/config/alfresco/messages/templates-messages_ja.properties +++ b/config/alfresco/messages/templates-messages_ja.properties @@ -83,6 +83,6 @@ templates.doc_info.no_document_found=\u6587\u66f8\u304c\u898b\u3064\u304b\u308a\ #invite_user_email.ftl templates.invite_user_email.invited_to_space={1}\u304b\u3089''{0}''\u306b\u62db\u5f85\u3055\u308c\u3066\u3044\u307e\u3059 -templates.invite_user_email.role=\u4ee5\u4e0b\u306e\u5f79\u5272\u304c\u3042\u308a\u307e\u3059: {0} +templates.invite_user_email.role=\u3042\u306a\u305f\u306e\u5f79\u5272: {0} templates.invite_user_email.you_can_view_the_space=Alfresco\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u3092\u4f7f\u7528\u3057\u3066\u30b9\u30da\u30fc\u30b9\u3092\u8868\u793a\u3067\u304d\u307e\u3059\u3002 templates.invite_user_email.regards=\u3088\u308d\u3057\u304f\u304a\u9858\u3044\u3057\u307e\u3059\u3002 diff --git a/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication-context.xml b/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication-context.xml index a8aac32ac8..aa28f4b2ae 100644 --- a/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication-context.xml +++ b/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication-context.xml @@ -76,9 +76,12 @@ true - - ${kerberos.authentication.cifs.enableTicketCracking} - + + ${kerberos.authentication.cifs.enableTicketCracking} + + + ${kerberos.authentication.stripUsernameSuffix} + \ No newline at end of file diff --git a/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication.properties b/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication.properties index 423defd9a3..608bab381a 100644 --- a/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication.properties +++ b/config/alfresco/subsystems/Authentication/kerberos/kerberos-authentication.properties @@ -4,4 +4,5 @@ kerberos.authentication.defaultAdministratorUserNames= kerberos.authentication.cifs.configEntryName=AlfrescoCIFS kerberos.authentication.cifs.password=secret kerberos.authentication.cifs.enableTicketCracking=false -kerberos.authentication.authenticateCIFS=true \ No newline at end of file +kerberos.authentication.authenticateCIFS=true +kerberos.authentication.stripUsernameSuffix=true \ No newline at end of file diff --git a/config/alfresco/subsystems/Authentication/ldap-ad/ldap-ad-authentication.properties b/config/alfresco/subsystems/Authentication/ldap-ad/ldap-ad-authentication.properties index b7af1b1393..f9fa167220 100644 --- a/config/alfresco/subsystems/Authentication/ldap-ad/ldap-ad-authentication.properties +++ b/config/alfresco/subsystems/Authentication/ldap-ad/ldap-ad-authentication.properties @@ -63,13 +63,13 @@ ldap.synchronization.attributeBatchSize=1000 ldap.synchronization.groupQuery=(objectclass\=group) # The query to select objects that represent the groups to import that have changed since a certain time. -ldap.synchronization.groupDifferentialQuery=(&(objectclass\=group)(!(modifyTimestamp<\={0}))) +ldap.synchronization.groupDifferentialQuery=(&(objectclass\=group)(!(whenChanged<\={0}))) # The query to select all objects that represent the users to import. ldap.synchronization.personQuery=(&(objectclass\=user)(userAccountControl\:1.2.840.113556.1.4.803\:\=512)) # The query to select objects that represent the users to import that have changed since a certain time. -ldap.synchronization.personDifferentialQuery=(&(objectclass\=user)(userAccountControl\:1.2.840.113556.1.4.803\:\=512)(!(modifyTimestamp<\={0}))) +ldap.synchronization.personDifferentialQuery=(&(objectclass\=user)(userAccountControl\:1.2.840.113556.1.4.803\:\=512)(!(whenChanged<\={0}))) # The group search base restricts the LDAP group query to a sub section of tree on the LDAP server. ldap.synchronization.groupSearchBase=ou\=Security Groups,ou\=Alfresco,dc=domain @@ -78,7 +78,7 @@ ldap.synchronization.groupSearchBase=ou\=Security Groups,ou\=Alfresco,dc=domain ldap.synchronization.userSearchBase=ou\=User Accounts,ou=\Alfresco,dc=domain # The name of the operational attribute recording the last update time for a group or user. -ldap.synchronization.modifyTimestampAttributeName=modifyTimestamp +ldap.synchronization.modifyTimestampAttributeName=whenChanged # The timestamp format. Unfortunately, this varies between directory servers. ldap.synchronization.timestampFormat=yyyyMMddHHmmss'.0Z' diff --git a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java index ef2d68dda4..d1adc9a7c8 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java @@ -112,6 +112,10 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement private boolean m_acceptNTLMv1 = true; + // Should we strip the @domain suffix from the Kerberos username? + + private boolean m_stripKerberosUsernameSuffix = true; + // Kerberos settings // // Account name and password for server ticket @@ -213,6 +217,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement { this.m_acceptNTLMv1 = !disallowNTLMv1; } + + public void setStripKerberosUsernameSuffix(boolean stripKerberosUsernameSuffix) + { + m_stripKerberosUsernameSuffix = stripKerberosUsernameSuffix; + } /** * Enable Kerbeors ticket cracking code that is required for Java5 @@ -1585,6 +1594,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Check if this is a null logon String userName = krbDetails.getUserName(); + String userId = m_stripKerberosUsernameSuffix ? krbDetails.getUserName() : krbDetails.getSourceName(); if ( userName != null) { @@ -1602,7 +1612,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Debug if ( logger.isDebugEnabled()) - logger.debug("Machine account logon, " + userName + ", as null logon"); + logger.debug("Machine account logon, " + userId + ", as null logon"); } else { @@ -1611,7 +1621,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement try { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; - getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(krbDetails.getUserName(), true)); + getAuthenticationComponent().setCurrentUser( mapUserNameToPerson(userId, true)); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket() ); } catch (AuthenticationException e) @@ -1622,11 +1632,9 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement } - // Store the full user name in the client information, indicate that this is not a guest logon - - // ALF-4599: CIFS access to alfresco creates wrong users with Realm suffix - // client.setUserName( krbDetails.getSourceName()); - client.setUserName( krbDetails.getUserName()); + // Store the user name in the client information, indicate that this is not a guest logon + + client.setUserName( userId); client.setGuest( false); // Indicate that the session is logged on diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java index 52343746da..0af000e8aa 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java @@ -1267,6 +1267,10 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD catch (IOException ex) { // Allow I/O Exceptions to pass through + if ( logger.isDebugEnabled()) + { + logger.debug("Delete file error - pass through IO Exception", ex); + } throw ex; } catch (Exception ex) @@ -1526,6 +1530,15 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD } //TODO MER Think we may need to implement, Temporary, Hidden, System, Archive + if(info.isSystem()) + { + logger.debug("Set system aspect (not yet implemented)" + name); + } + if(info.isTemporary()) + { + logger.debug("Set temporary aspect (not yet implemented)" + name); + } + if(info.isHidden()) { // yes is hidden @@ -1561,6 +1574,24 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD } // Not yet implemented } + + if( info.hasSetFlag(FileInfo.SetFileSize)) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Set file size" + name + info.getSize()); + } + // Not yet implemented + } + + if( info.hasSetFlag(FileInfo.SetMode)) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Set Mode" + name + info.getMode()); + } + // Not yet implemented - set the unix mode e.g. 777 + } // Set the creation and modified date/time Map auditableProps = new HashMap(5); @@ -2634,7 +2665,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD { if ( logger.isDebugEnabled()) { - logger.debug("Close file:" + path); + logger.debug("Close file:" + path + ", readOnly=" + file.isReadOnly() ); } if( file instanceof PseudoNetworkFile) @@ -2650,7 +2681,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD { if(logger.isDebugEnabled()) { - logger.debug("closeFile has delete on close set"); + logger.debug("closeFile has delete on close set path:" + path); } try { @@ -2686,7 +2717,7 @@ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedD { if(logger.isDebugEnabled()) { - logger.debug("Got a temp network file to close"); + logger.debug("Got a temp network file to close path:" + path); } // Some content was written to the temp file. diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java index dffcaf542d..cc2913a284 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java @@ -96,6 +96,8 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.FileFilterMode; +import org.alfresco.util.FileFilterMode.Client; import org.alfresco.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -2488,7 +2490,7 @@ public class ContentDiskDriverTest extends TestCase final String FILE_NAME = "ContentDiskDriver.docx"; //final String FILE_OLD_TEMP = "._Word Work File D_1725484373.tmp"; final String FILE_NEW_TEMP = "Word Work File D_1725484373.tmp"; - + class TestContext { NodeRef testDirNodeRef; @@ -2503,7 +2505,7 @@ public class ContentDiskDriverTest extends TestCase ServerConfiguration scfg = new ServerConfiguration("testServer"); TestServer testServer = new TestServer("testServer", scfg); - final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + final SrvSession testSession = new TestSrvSession(666, testServer, "cifs", "remoteName"); DiskSharedDevice share = getDiskSharedDevice(); final TreeConnection testConnection = testServer.getTreeConnection(share); final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); @@ -2694,6 +2696,8 @@ public class ContentDiskDriverTest extends TestCase // Check that the temporary aspect has been applied. assertTrue("temporary aspect has not been removed", !nodeService.hasAspect(testContext.testNodeRef, ContentModel.ASPECT_TEMPORARY)); + assertTrue("hidden aspect has not been removed", !nodeService.hasAspect(testContext.testNodeRef, ContentModel.ASPECT_HIDDEN)); + // These metadata values should be extracted. @@ -3553,6 +3557,144 @@ public class ContentDiskDriverTest extends TestCase tran.doInTransaction(validateCB, false, true); } // testOpenCloseFileScenario + + + /** + * Test Open Close File Scenario II ALF-13401 + * Open Read Only of a file already open for read/write. + * + * 1) open(readWrite) + * 2) write some content. + * 3) open(readOnly). + * 4) read some content. + * 5) close - updates the repo + */ + public void testScenarioOpenCloseFileTwo() throws Exception + { + logger.debug("start of testScenarioOpenCloseFileTwo"); + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + class TestContext + { + NodeRef testDirNodeRef; + NodeRef targetNodeRef; + }; + + final TestContext testContext = new TestContext(); + + final String FILE_NAME="testScenarioOpenFileTwo.txt"; + final String FILE_PATH= TEST_ROOT_DOS_PATH + "\\" + FILE_NAME; + + FileOpenParams dirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadOnly, FileAttribute.NTDirectory, 0); + driver.createDirectory(testSession, testConnection, dirParams); + + testContext.testDirNodeRef = getNodeForPath(testConnection, TEST_ROOT_DOS_PATH); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteGarbageFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, FILE_PATH); + return null; + } + }; + try + { + tran.doInTransaction(deleteGarbageFileCB); + } + catch (Exception e) + { + // expect to go here + } + + /** + * Step 1: Now create the file through the node service and open it. + */ + logger.debug("Step 1) Create File and Open file created by node service"); + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + logger.debug("create file and close it immediatly"); + FileOpenParams createFileParams = new FileOpenParams(FILE_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + NetworkFile dummy = driver.createFile(testSession, testConnection, createFileParams); + driver.closeFile(testSession, testConnection, dummy); + logger.debug("after create and close"); + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + testContext.targetNodeRef = getNodeForPath(testConnection, FILE_PATH); + + FileOpenParams openRO = new FileOpenParams(FILE_PATH, FileAction.CreateNotExist, AccessMode.ReadOnly, FileAttribute.NTNormal, 0); + FileOpenParams openRW = new FileOpenParams(FILE_PATH, FileAction.CreateNotExist, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + + /** + * First open - read write + */ + logger.debug("open file1 read only"); + NetworkFile file1 = driver.openFile(testSession, testConnection, openRW); + assertNotNull(file1); + + final String testString = "Yankee doodle went to town, riding on a donkey."; + byte[] stuff = testString.getBytes("UTF-8"); + + driver.writeFile(testSession, testConnection, file1, stuff, 0, stuff.length, 0); + + logger.debug("open file 2 for read only"); + NetworkFile file2 = driver.openFile(testSession, testConnection, openRO); + assertNotNull(file2); + + + assertTrue("file size is 0", file2.getFileSize() > 0); + + + /** + * Write Some More Content + */ + driver.writeFile(testSession, testConnection, file1, stuff, 0, stuff.length, 0); + + logger.debug("first close"); + driver.closeFile(testSession, testConnection, file2); + + logger.debug("second close"); + driver.closeFile(testSession, testConnection, file1); + + logger.debug("now validate"); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + Map props = nodeService.getProperties(testContext.targetNodeRef); + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + assertNotNull("data is null", data); + assertEquals("data wrong length", testString.length(), data.getSize()); + + ContentReader reader = contentService.getReader(testContext.targetNodeRef, ContentModel.PROP_CONTENT); + String s = reader.getContentString(); + assertEquals("content not written", testString, s); + + return null; + } + }; + + tran.doInTransaction(validateCB, false, true); + + } // testOpenCloseFileScenarioTwo + @@ -4774,7 +4916,198 @@ public class ContentDiskDriverTest extends TestCase } // testScenarioLionTextEdit - + /** + * Simulates a Save from Powerpoint 2011 Mac + * 0. FileA.pptx already exists. + * 1. Create new document FileA1.pptx + * 2. Delete FileA.pptx + * 3. Rename FileA1.pptx to FileA.pptx + */ + public void testScenarioMSPowerpoint2011MacSaveShuffle() throws Exception + { + logger.debug("testScenarioMSPowerpoint2011MacSaveShuffle("); + + final String FILE_NAME = "FileA.pptx"; + final String FILE_NEW_TEMP = "FileA1.pptx"; + + class TestContext + { + NetworkFile firstFileHandle; + }; + + final TestContext testContext = new TestContext(); + + final String TEST_DIR = TEST_ROOT_DOS_PATH + "\\testScenarioMSPowerpoint2011MacSaveShuffle"; + + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "cifs", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteGarbageFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + /** + * Create a file in the test directory + */ + + try + { + tran.doInTransaction(deleteGarbageFileCB); + } + catch (Exception e) + { + // expect to go here + } + + logger.debug("0) create new file"); + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + + /** + * Create the test directory we are going to use + */ + FileOpenParams createRootDirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + FileOpenParams createDirParams = new FileOpenParams(TEST_DIR, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + driver.createDirectory(testSession, testConnection, createRootDirParams); + driver.createDirectory(testSession, testConnection, createDirParams); + + /** + * Create the file we are going to use (FileA.pptx) + */ + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + assertNotNull(testContext.firstFileHandle); + + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); + + NodeRef file1NodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + nodeService.addAspect(file1NodeRef, ContentModel.ASPECT_VERSIONABLE, null); + + + + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + /** + * b) Save the new file + * Write ContentDiskDriverTest3.doc to the test file, + */ + logger.debug("b) write some content"); + RetryingTransactionCallback writeFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NEW_TEMP, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + testContext.firstFileHandle = driver.createFile(testSession, testConnection, createFileParams); + + ClassPathResource fileResource = new ClassPathResource("filesys/ContentDiskDriverTest3.doc"); + assertNotNull("unable to find test resource filesys/ContentDiskDriverTest3.doc", fileResource); + + byte[] buffer= new byte[1000]; + InputStream is = fileResource.getInputStream(); + try + { + long offset = 0; + int i = is.read(buffer, 0, buffer.length); + while(i > 0) + { + testContext.firstFileHandle.writeFile(buffer, i, 0, offset); + offset += i; + i = is.read(buffer, 0, buffer.length); + } + } + finally + { + is.close(); + } + + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); + + return null; + } + }; + tran.doInTransaction(writeFileCB, false, true); + + /** + * c) delete the old file + */ + logger.debug("c) delete old file"); + RetryingTransactionCallback renameOldFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + tran.doInTransaction(renameOldFileCB, false, true); + + /** + * d) Move the new file into place, stuff should get shuffled + */ + logger.debug("d) rename new file into place"); + RetryingTransactionCallback moveNewFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.renameFile(testSession, testConnection, TEST_DIR + "\\" + FILE_NEW_TEMP, TEST_DIR + "\\" + FILE_NAME); + return null; + } + }; + + tran.doInTransaction(moveNewFileCB, false, true); + + logger.debug("e) validate results"); + /** + * Now validate everything is correct + */ + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + + Map props = nodeService.getProperties(shuffledNodeRef); + + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + assertNotNull("data is null", data); + assertEquals("size is wrong", 26112, data.getSize()); + assertEquals("mimeType is wrong", "application/msword",data.getMimetype()); + + assertTrue("versionable aspect missing", nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_VERSIONABLE)); + assertTrue("hidden aspect still applied", !nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_HIDDEN)); + assertTrue("temporary aspect still applied", !nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_TEMPORARY)); + + return null; + } + }; + + tran.doInTransaction(validateCB, true, true); + + } // testScenarioMSPowerpoint2011MacSaveShuffle + /** diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java index c84067c019..d80aa1e693 100644 --- a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java @@ -66,6 +66,11 @@ import org.apache.commons.logging.LogFactory; * 4) close(readWrite) - does nothing. Decrements Open Count. * 5) close(readWrite) - updates the repo. * 6) close(readOnly) - closes read only + *

+ * 1) open (readWrite) + * 2) open (readOnly) - file already open for read/write + * 3) close + * 4) close * */ class ScenarioOpenFileInstance implements ScenarioInstance @@ -307,7 +312,7 @@ class ScenarioOpenFileInstance implements ScenarioInstance if(name != null && name.equalsIgnoreCase(o.getName())) { if(o.getMode() == OpenFileMode.READ_WRITE) - { + { // This is an open of a read write access if(openReadWriteCount == 0) { @@ -329,6 +334,15 @@ class ScenarioOpenFileInstance implements ScenarioInstance else { // This is an open for read only access + + if(openReadWriteCount > 0) + { + //however the file is already open for read/write + openReadWriteCount++; + logger.debug("Return already open read/write file handle from scenario:" + this); + return new ReturnValueCommand(fileHandleReadWrite); + } + if(openReadOnlyCount == 0) { logger.debug("Open first read only from scenario:" + this); diff --git a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java index 4229a6c50a..c684ef7fcb 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java @@ -469,14 +469,22 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli */ public Invitation cancel(String invitationId) { - WorkflowTask startTask = getStartTask(invitationId); - if(taskTypeMatches(startTask, WorkflowModelModeratedInvitation.WF_START_TASK)) + try { - return cancelModeratedInvitation(startTask); + WorkflowTask startTask = getStartTask(invitationId); + if (taskTypeMatches(startTask, WorkflowModelModeratedInvitation.WF_START_TASK)) + { + return cancelModeratedInvitation(startTask); + } + else + { + return cancelNominatedInvitation(startTask); + } } - else + catch (InvitationExceptionNotFound e) { - return cancelNominatedInvitation(startTask); + // Invitation already deleted or deleted in background + return null; } } diff --git a/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngineTest.java b/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngineTest.java index 13d7043555..438acb8fa5 100644 --- a/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngineTest.java +++ b/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngineTest.java @@ -35,6 +35,7 @@ import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateException; import org.alfresco.service.cmr.repository.TemplateProcessor; import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.search.ResultSet; @@ -45,6 +46,7 @@ import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + /** * @author Brian * @@ -82,6 +84,33 @@ public class XSLTRenderingEngineTest extends BaseAlfrescoSpringTest "/app:company_home"); this.companyHome = rs.getNodeRef(0); } + + public void testSecurityFilter() throws Exception + { + try + { + FileInfo file = createXmlFile(companyHome); + FileInfo xslFile = createXmlFile(companyHome, insecureVerySimpleXSLT); + + RenditionDefinition def = renditionService.createRenditionDefinition(QName.createQName("Test"), XSLTRenderingEngine.NAME); + def.setParameterValue(XSLTRenderingEngine.PARAM_TEMPLATE_NODE, xslFile.getNodeRef()); + + ChildAssociationRef rendition = renditionService.render(file.getNodeRef(), def); + log.error("This insecure template should not process!"); + fail(); + + } + catch (TemplateException e) + { + //pass! + } + catch (Exception ex) + { + + log.error("Error!", ex); + fail(); + } + } public void testSimplestStringTemplate() throws Exception { @@ -318,6 +347,16 @@ public class XSLTRenderingEngineTest extends BaseAlfrescoSpringTest "" + "" + "" + "" + "" + ""; + private String insecureVerySimpleXSLT = "" + " " + + "xmlns:fn=\"http://www.w3.org/2005/02/xpath-functions\"> " + "" + + + "" + + + "" + "" + + "" + "" + "" + ""; + private String callParseXmlDocument = "" + " " + "" + diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java index 1da81bc917..0459a1a30a 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java @@ -5036,11 +5036,11 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener assertEquals(1, results.length()); results.close(); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "ASPECT:\"" + testAspect.toString() + "\"", null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "EXACTASPECT:\"" + testAspect.toString() + "\"", null); assertEquals(1, results.length()); results.close(); - results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "ASPECT:\"" + testAspect.toPrefixString(namespacePrefixResolver) + "\"", null); + results = searcher.query(rootNodeRef.getStoreRef(), "lucene", "EXACTASPECT:\"" + testAspect.toPrefixString(namespacePrefixResolver) + "\"", null); assertEquals(1, results.length()); results.close(); diff --git a/source/java/org/alfresco/repo/template/XSLTProcessor.java b/source/java/org/alfresco/repo/template/XSLTProcessor.java index e988073d36..619b5342f3 100644 --- a/source/java/org/alfresco/repo/template/XSLTProcessor.java +++ b/source/java/org/alfresco/repo/template/XSLTProcessor.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; +import java.io.StringWriter; import java.io.Writer; import java.util.Arrays; import java.util.HashMap; @@ -134,7 +135,7 @@ public class XSLTProcessor extends BaseProcessor implements TemplateProcessor Document xslTemplate; try { - xslTemplate = XMLUtil.parse(templateSource.getReader(defaultEncoding)); + xslTemplate = XMLUtil.secureParseXSL(templateSource.getReader(defaultEncoding)); } catch (IOException ex) { @@ -314,18 +315,17 @@ public class XSLTProcessor extends BaseProcessor implements TemplateProcessor final Element docEl = xslTemplate.getDocumentElement(); final String XALAN_NS = Constants.S_BUILTIN_EXTENSIONS_URL; final String XALAN_NS_PREFIX = "xalan"; - docEl.setAttribute("xmlns:" + XALAN_NS_PREFIX, XALAN_NS); final Set excludePrefixes = new HashSet(); if (docEl.hasAttribute("exclude-result-prefixes")) { excludePrefixes.addAll(Arrays.asList(docEl.getAttribute("exclude-result-prefixes").split(" "))); } - excludePrefixes.add(XALAN_NS_PREFIX); final List result = new LinkedList(); for (QName ns : methods.keySet()) { + final String prefix = ns.getLocalName(); docEl.setAttribute("xmlns:" + prefix, ns.getNamespaceURI()); excludePrefixes.add(prefix); @@ -365,6 +365,11 @@ public class XSLTProcessor extends BaseProcessor implements TemplateProcessor } docEl.setAttribute("exclude-result-prefixes", StringUtils.join(excludePrefixes .toArray(new String[excludePrefixes.size()]), " ")); + if (log.isDebugEnabled()) { + StringWriter writer = new StringWriter(); + XMLUtil.print(xslTemplate, writer); + log.debug(writer); + } return result; } diff --git a/source/java/org/alfresco/repo/template/XSLTProcessorTest.java b/source/java/org/alfresco/repo/template/XSLTProcessorTest.java index 21c9413a66..e7a0d18444 100644 --- a/source/java/org/alfresco/repo/template/XSLTProcessorTest.java +++ b/source/java/org/alfresco/repo/template/XSLTProcessorTest.java @@ -30,6 +30,7 @@ import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateException; import org.alfresco.service.cmr.repository.TemplateProcessor; import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.search.ResultSet; @@ -75,6 +76,30 @@ public class XSLTProcessorTest extends BaseAlfrescoSpringTest "/app:company_home"); this.companyHome = rs.getNodeRef(0); } + + public void testSecurityFilter() throws Exception + { + try + { + FileInfo file = createXmlFile(companyHome); + XSLTemplateModel model = new XSLTemplateModel(); + model.put(XSLTProcessor.ROOT_NAMESPACE, XMLUtil.parse(file.getNodeRef(), contentService)); + + StringWriter writer = new StringWriter(); + xsltProcessor.processString(insecureVerySimpleXSLT, model, writer); + log.error("This insecure template should not process!"); + fail(); + } + catch (TemplateException e) + { + //pass! + } + catch (Exception ex) + { + log.error("Error!", ex); + fail(); + } + } public void testSimplestStringTemplate() throws Exception { @@ -267,4 +292,14 @@ public class XSLTProcessorTest extends BaseAlfrescoSpringTest "" + "" + "" + "" + "" + ""; + + private String insecureVerySimpleXSLT = "" + " " + + "xmlns:fn=\"http://www.w3.org/2005/02/xpath-functions\"> " + "" + + + "" + + + "" + "" + + "" + "" + "" + ""; } diff --git a/source/java/org/alfresco/util/XMLUtil.java b/source/java/org/alfresco/util/XMLUtil.java index 683e356805..7738c8b166 100644 --- a/source/java/org/alfresco/util/XMLUtil.java +++ b/source/java/org/alfresco/util/XMLUtil.java @@ -19,7 +19,18 @@ package org.alfresco.util; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.LinkedList; +import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -28,8 +39,12 @@ import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; + import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.repository.ContentReader; @@ -37,9 +52,19 @@ import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.w3c.dom.*; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.XMLFilter; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLFilterImpl; +import org.xml.sax.helpers.XMLReaderFactory; /** * XML utility functions. @@ -114,75 +139,229 @@ public class XMLUtil } /** utility function for parsing xml */ - public static Document parse(final String source) + public static Document parse(final String source, final XMLFilter... filters) throws SAXException, IOException { - return XMLUtil.parse(new ByteArrayInputStream(source.getBytes("UTF-8"))); + return XMLUtil.parse(new CharArrayReader(source.toCharArray()), filters); + } + + public static Document secureParseXSL (final String source, final XMLFilter... filters) + throws SAXException, + IOException + { + return parse(new CharArrayReader(source.toCharArray()), addSecurityFilter(filters)); } /** utility function for parsing xml */ public static Document parse(final NodeRef nodeRef, - final ContentService contentService) + final ContentService contentService, + final XMLFilter... filters) throws SAXException, IOException { final ContentReader contentReader = contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT); final InputStream in = contentReader.getContentInputStream(); - return XMLUtil.parse(in); + return XMLUtil.parse(in, filters); } + + public static Document secureParseXSL(final NodeRef nodeRef, + final ContentService contentService, + final XMLFilter... filters) + throws SAXException, + IOException + { + final ContentReader contentReader = + contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT); + final InputStream in = contentReader.getContentInputStream(); + return parse(in, addSecurityFilter(filters)); + } /** utility function for parsing xml */ public static Document parse(final int version, final String path, - final AVMService avmService) + final AVMService avmService, + final XMLFilter...filters) throws SAXException, IOException { - return XMLUtil.parse(avmService.getFileInputStream(version, path)); + return XMLUtil.parse(avmService.getFileInputStream(version, path), filters); + } + + public static Document secureParseXSL(final int version, + final String path, + final AVMService avmService, + final XMLFilter... filters) + throws SAXException, + IOException + { + return parse(avmService.getFileInputStream(version, path), addSecurityFilter(filters)); } /** utility function for parsing xml */ - public static Document parse(final File source) + public static Document parse(final File source, + final XMLFilter... filters) throws SAXException, IOException { - return XMLUtil.parse(new FileInputStream(source)); + return XMLUtil.parse(new FileInputStream(source), filters); + } + + public static Document secureParseXSL(final File source, + final XMLFilter... filters) + throws SAXException, + IOException + { + return parse(new FileInputStream(source), addSecurityFilter(filters)); + } + + private static Document parseWithXMLFilters(final InputSource source, + final XMLFilter... filters) + throws SAXException, + IOException + { + return parseWithXMLFilters(source, false, filters); + } + + private static Document parseWithXMLFilters(final InputSource source, + final boolean validating, + final XMLFilter... filters) + throws SAXException, + IOException + { + TransformerFactory tf = TransformerFactory.newInstance(); + // Check to make sure this is a SAX TransformerFactory + if (!tf.getFeature(SAXTransformerFactory.FEATURE)) + { + throw new SAXException("SAX Transformation factory not found."); + } + // Cast to appropriate factory class + SAXTransformerFactory stf = (SAXTransformerFactory) tf; + final DocumentBuilder db = XMLUtil.getDocumentBuilder(true, validating); + + if (filters == null || filters.length == 0) + { + // No filters. Process this as normal. + return db.parse(source); + } + else + { + // Process with filters + try + { + final Document doc = db.newDocument(); + final TransformerHandler th = stf.newTransformerHandler(); + // Specify transformation to DOMResult with empty Node container (Document) + th.setResult(new DOMResult(doc)); + XMLReader reader = XMLReaderFactory.createXMLReader(); + + //emulate what the document builder parser supports + //all readers are required to support namespaces and namespace-prefixes + reader.setFeature("http://xml.org/sax/features/namespaces", db.isNamespaceAware()); + reader.setFeature("http://xml.org/sax/features/namespace-prefixes", db.isNamespaceAware() ? true : false); + + // Chain multiple filters together + int i = 0; + XMLFilter filter = null; + for (XMLFilter f : filters) + { + // there can be no null in the filter list + if (f == null) + throw new SAXException("Nulls are not allowed in XML filter list."); + // if first item then set new reader + if (i == 0) + f.setParent(reader); + else + // set parent filter to previous element in the array + f.setParent(filters[i - 1]); + + filter = f; + i++; + } + //not sure how filter could be null + if (filter != null) + { + filter.setContentHandler(th); + filter.parse(source); + try + { + //try to activate/deactivate validation + filter.setFeature("http://xml.org/sax/features/validation", db.isValidating()); + } + catch (SAXException se) + { + LOGGER.warn("XML reader does not support validation feature.", se); + } + } + else + { + //not sure how we could get here + throw new SAXException("No XML filters available to process this request."); + } + if (LOGGER.isDebugEnabled()) { + StringWriter writer = new StringWriter(); + XMLUtil.print(doc, writer); + LOGGER.debug(writer); + } + return doc; + } + catch (TransformerException tce) + { + throw new SAXException(tce); + } + } } /** utility function for parsing xml */ - public static Document parse(final InputStream source) + public static Document parse(final InputStream source, final XMLFilter... filters) throws SAXException, IOException { try { - final DocumentBuilder db = XMLUtil.getDocumentBuilder(); - return db.parse(source); + return parseWithXMLFilters(new InputSource(source), filters); } finally { source.close(); } } + + /** secure parse for InputStream source */ + public static Document secureParseXSL(final InputStream source, + final XMLFilter... filters) + throws SAXException, + IOException + { + return parse(source, addSecurityFilter(filters)); + } /** utility function for parsing xml */ - public static Document parse(final Reader source) + public static Document parse(final Reader source, + final XMLFilter... filters) throws SAXException, IOException { try { - final DocumentBuilder db = XMLUtil.getDocumentBuilder(); - return db.parse(new InputSource(source)); + return parseWithXMLFilters(new InputSource(source), filters); } finally { source.close(); } } - + + /** secure parse for Reader source **/ + public static Document secureParseXSL(final Reader source, + final XMLFilter... filters) + throws SAXException, + IOException + { + return parse(source, addSecurityFilter(filters)); + } + /** provides a document builder that is namespace aware but not validating by default */ public static DocumentBuilder getDocumentBuilder() { @@ -293,4 +472,76 @@ public class XMLUtil } }; } + + /** + * returns a new array of filters with the security filter at the head of the array + */ + private static XMLFilter[] addSecurityFilter(XMLFilter...filters) { + if (filters == null || filters.length == 0) { + return new XMLFilter[] {new FastFailSecureXMLFilter()}; + } else { + XMLFilter[] xmlfilters = new XMLFilter[filters.length + 1]; + xmlfilters[0] = new FastFailSecureXMLFilter(); + System.arraycopy(filters, 0, xmlfilters, 1, filters.length); + return xmlfilters; + } + } + + /** + * XMLFilter that throws an exception when it comes across any insecure namespaces + */ + private static class FastFailSecureXMLFilter extends XMLFilterImpl + { + + private static final List insecureURIs = new LinkedList() + { + private static final long serialVersionUID = 1L; + + { + add("xalan://"); + add("http://xml.apache.org/xalan/java"); + add("http://xml.apache.org/xslt/java"); + add("http://xml.apache.org/java"); + } + }; + + public FastFailSecureXMLFilter() + { + }; + + public void startPrefixMapping(String prefix, String uri) + throws SAXException + { + if (isInsecureURI(uri)) + { + throw new SAXException("Insecure namespace: " + uri); + } + super.startPrefixMapping(prefix, uri); + } + + + public void startElement (String uri, + String localName, + String qName, + final Attributes atts) + throws SAXException + { + + if (isInsecureURI(uri)) + { + throw new SAXException("Insecure namespace: " + uri); + } + super.startElement(uri, localName, qName, atts); + } + + private boolean isInsecureURI(String uri) + { + for (String insecureURI : insecureURIs) + { + if (uri.startsWith(insecureURI)) return true; + } + return false; + } + + } }