REPO-5094: Allow switching between existing content store subsystems (#1101)

- Update CryptodocSwitchableApplicationContextFactory to accept switching between existing content store subsystems, not just based on plain bean names
- Add EncryptedContentStoreChildApplicationContextFactory as extension of ChildApplicationContextFactory with support to specify if the content store in the subsystem is encrypted or not
This commit is contained in:
Ancuta Morarasu
2020-07-16 16:46:10 +03:00
committed by GitHub
parent 76a644f76b
commit 4b9996d204
5 changed files with 296 additions and 152 deletions

View File

@@ -1,121 +1,58 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* 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/>.
* #L%
*/
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* 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/>.
* #L%
*/
package org.alfresco.repo.management.subsystems;
import java.io.IOException;
import org.alfresco.repo.descriptor.DescriptorServiceAvailableEvent;
import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.license.LicenseDescriptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationEvent;
import java.io.IOException;
/**
* {@link SwitchableApplicationContextFactory} that only allows the subsystem to be switched
* if the current subsystem is the unencrypted content store. When an attempt is made to switch away
* from any other store (e.g. the encrypted store) then nothing happens. This is achieved by returning
* <code>false</code> for {@link #isUpdateable(String)} calls when the current sourceBeanName is
* that of the unencrypted content store's subsystem.
*
* {@link SwitchableApplicationContextFactory} that only allows the subsystem to be switched from unencrypted to encrypted,
* or if the two subsystems have the same ecrypted state.
* Switching back to unencrypted from encrypted content store is not allowed.
*
* @author Matt Ward
*/
public class CryptodocSwitchableApplicationContextFactory extends SwitchableApplicationContextFactory
{
private static final String SOURCE_BEAN_PROPERTY = "sourceBeanName";
private String unencryptedContentStoreBeanName;
private String encryptedContentStoreBeanName;
private DescriptorService descriptorService;
private static final Log logger = LogFactory.getLog(CryptodocSwitchableApplicationContextFactory.class);
@Override
public boolean isUpdateable(String name)
{
if (name == null)
{
throw new IllegalStateException("Property name cannot be null");
}
boolean updateable = true;
if (name.equals(SOURCE_BEAN_PROPERTY))
{
if(getCurrentSourceBeanName().equals(unencryptedContentStoreBeanName))
{
if(descriptorService != null)
{
LicenseDescriptor license = descriptorService.getLicenseDescriptor();
if(license != null && license.isCryptodocEnabled())
{
return true;
}
return false;
}
}
// can the source bean name be changed?
if(!getCurrentSourceBeanName().equals(unencryptedContentStoreBeanName))
{
// the subsystem has been switched once.
return false;
}
}
return updateable;
}
@Override
protected PropertyBackedBeanState createInitialState() throws IOException
{
return new CryptoSwitchableState(sourceBeanName);
}
/**
* The bean name of the unencrypted ContentStore subsystem.
*
* @param unencryptedContentStoreBeanName String
*/
public void setUnencryptedContentStoreBeanName(String unencryptedContentStoreBeanName)
{
this.unencryptedContentStoreBeanName = unencryptedContentStoreBeanName;
}
public String getEncryptedContentStoreBeanName()
{
return encryptedContentStoreBeanName;
}
public void setEncryptedContentStoreBeanName(
String encryptedContentStoreBeanName)
{
this.encryptedContentStoreBeanName = encryptedContentStoreBeanName;
}
protected class CryptoSwitchableState extends SwitchableState
{
protected CryptoSwitchableState(String sourceBeanName)
@@ -126,25 +63,77 @@ public class CryptodocSwitchableApplicationContextFactory extends SwitchableAppl
@Override
public void setProperty(String name, String value)
{
if (!isUpdateable(name))
if (name.equals(SOURCE_BEAN_PROPERTY))
{
if(value.equalsIgnoreCase(unencryptedContentStoreBeanName))
{
throw new IllegalStateException("Switching to an unencrypted content store is not possible.");
}
if(value.equalsIgnoreCase(encryptedContentStoreBeanName))
{
throw new IllegalStateException("Switching to an encrypted content store is not licensed.");
}
throw new IllegalStateException("Switching to an unknown content store is not possible." + value);
ChildApplicationContextFactory newSourceBean;
try
{
newSourceBean = getParent().getBean(value, ChildApplicationContextFactory.class);
}
catch (BeansException e)
{
throw new IllegalStateException("Switching to the unknown content store \"" + value + "\" is not possible.");
}
if (canSwitchSubsystemTo(newSourceBean, value))
{
boolean isNewEncrypted = isEncryptedContentStoreSubsystem(newSourceBean, value);
if (isNewEncrypted && !isEncryptionSupported())
{
throw new IllegalStateException("Switching to the encrypted content store \"" + value + "\" is not licensed.");
}
}
else
{
throw new IllegalStateException("Switching to the unencrypted content store \"" + value + "\" is not possible.");
}
}
super.setProperty(name, value);
}
}
private boolean canSwitchSubsystemTo(Object newSourceBean, String beanName)
{
Object currentSourceBean = getParent().getBean(getCurrentSourceBeanName());
boolean isCurrentEncrypted = isEncryptedContentStoreSubsystem(currentSourceBean, getCurrentSourceBeanName());
// Can switch from an unencrypted content store to any kind of content store
if (!isCurrentEncrypted)
{
return true;
}
boolean isNewEncrypted = isEncryptedContentStoreSubsystem(newSourceBean, beanName);
// Can switch from an encrypted content store only to another encrypted one
return isCurrentEncrypted && isNewEncrypted;
}
private boolean isEncryptedContentStoreSubsystem(Object sourceBean, String beanName)
{
boolean isEncrypted = false;
if (sourceBean instanceof EncryptedContentStoreChildApplicationContextFactory)
{
isEncrypted = ((EncryptedContentStoreChildApplicationContextFactory) sourceBean).isEncryptedContent();
}
//If not explicitly set as encrypted, check if the source bean is the cryptodoc subsystem bean
if (!isEncrypted)
{
isEncrypted = beanName.equals("encryptedContentStore");
}
return isEncrypted;
}
private boolean isEncryptionSupported()
{
boolean isSupported = true;
if (descriptorService != null)
{
LicenseDescriptor license = descriptorService.getLicenseDescriptor();
isSupported = license != null && license.isCryptodocEnabled();
}
return isSupported;
}
public void onApplicationEvent(ApplicationEvent event)
{
if(logger.isDebugEnabled())
{
logger.debug("event : " + event);

View File

@@ -0,0 +1,45 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* 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/>.
* #L%
*/
package org.alfresco.repo.management.subsystems;
/**
* ChildApplicationContextFactory extension used to identify the type of the content store subsystem, encrypted or unecrypted.
* See {@link CryptodocSwitchableApplicationContextFactory} for how is used.
*/
public class EncryptedContentStoreChildApplicationContextFactory extends ChildApplicationContextFactory
{
private boolean encryptedContent;
public boolean isEncryptedContent()
{
return encryptedContent;
}
public void setEncryptedContent(boolean encryptedContent)
{
this.encryptedContent = encryptedContent;
}
}

View File

@@ -1,28 +1,28 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* 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/>.
* #L%
*/
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* 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/>.
* #L%
*/
package org.alfresco.repo.management.subsystems;
import java.io.IOException;
@@ -41,7 +41,7 @@ public class SwitchableApplicationContextFactory extends AbstractPropertyBackedB
{
/** The name of the property holding the bean name of the source {@link ApplicationContextFactory}. */
private static final String SOURCE_BEAN_PROPERTY = "sourceBeanName";
protected static final String SOURCE_BEAN_PROPERTY = "sourceBeanName";
/** The default bean name of the source {@link ApplicationContextFactory}. */
protected String sourceBeanName;

View File

@@ -22,8 +22,6 @@
<value>manager</value>
</list>
</property>
<property name="unencryptedContentStoreBeanName" value="unencryptedContentStore"/>
<property name="encryptedContentStoreBeanName" value="encryptedContentStore"/>
</bean>
<!-- Default ContentStore subsystem, that does not use encryption -->
@@ -229,9 +227,9 @@
</property>
<property name="jsonObjectMapper" ref="mimetypeServiceJsonObjectMapper" />
<property name="mimetypeJsonConfigDir" value="${mimetype.config.dir}" />
<property name="cronExpression" value="${mimetype.config.cronExpression}"></property>
<property name="initialAndOnErrorCronExpression" value="${mimetype.config.initialAndOnError.cronExpression}"></property>
<property name="shutdownIndicator" ref="shutdownIndicator"></property>
<property name="cronExpression" value="${mimetype.config.cronExpression}" />
<property name="initialAndOnErrorCronExpression" value="${mimetype.config.initialAndOnError.cronExpression}" />
<property name="shutdownIndicator" ref="shutdownIndicator" />
</bean>
<bean id="mimetypeServiceJsonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />

View File

@@ -30,6 +30,9 @@ import static org.mockito.Mockito.*;
import java.util.Properties;
import org.alfresco.repo.descriptor.DescriptorServiceAvailableEvent;
import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.license.LicenseDescriptor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,60 +48,133 @@ import org.springframework.context.ApplicationContext;
@RunWith(MockitoJUnitRunner.class)
public class CryptodocSwitchableApplicationContextFactoryTest
{
private static final String UNENCRYPTED_STORE_SUBSYSTEM = "unencrypted";
private static final String ENCRYPTED_STORE_SUBSYSTEM = "encrypted";
private static final String UNENCRYPTED_STORE_SUBSYSTEM = "unencryptedContentStore";
private static final String NEW_UNENCRYPTED_STORE_SUBSYSTEM = "newUnencryptedContentStore";
private static final String ENCRYPTED_STORE_SUBSYSTEM = "encryptedContentStore";
private static final String NEW_STORE_SUBSYSTEM = "newContentStore";
private static final String UNKNOWN_STORE_SUBSYSTEM = "unknownBean";
private static final String SOURCE_BEAN_NAME_PROPERTY = "sourceBeanName";
// The class under test
private CryptodocSwitchableApplicationContextFactory switchableContext;
private @Mock ChildApplicationContextFactory unencrytedContentStore;
private @Mock ChildApplicationContextFactory newUnencrytedContentStore;
private @Mock ChildApplicationContextFactory cryptodocContentStore;
private @Mock EncryptedContentStoreChildApplicationContextFactory newContentStore;
private @Mock PropertyBackedBeanRegistry propertyBackedBeanRegistry;
private @Mock ApplicationContext parentContext;
private @Mock DescriptorService descriptorService;
private @Mock LicenseDescriptor licenseDescriptor;
@Before
public void setUp() throws Exception
{
switchableContext = new CryptodocSwitchableApplicationContextFactory();
when(parentContext.getBean(UNENCRYPTED_STORE_SUBSYSTEM)).thenReturn(unencrytedContentStore);
when(parentContext.getBean(UNENCRYPTED_STORE_SUBSYSTEM, ChildApplicationContextFactory.class)).thenReturn(unencrytedContentStore);
when(parentContext.containsBean(NEW_UNENCRYPTED_STORE_SUBSYSTEM)).thenReturn(true);
when(parentContext.getBean(NEW_UNENCRYPTED_STORE_SUBSYSTEM, ChildApplicationContextFactory.class)).thenReturn(newUnencrytedContentStore);
when(parentContext.containsBean(ENCRYPTED_STORE_SUBSYSTEM)).thenReturn(true);
when(parentContext.containsBean(UNENCRYPTED_STORE_SUBSYSTEM)).thenReturn(true);
when(parentContext.getBean(ENCRYPTED_STORE_SUBSYSTEM)).thenReturn(cryptodocContentStore);
when(parentContext.getBean(ENCRYPTED_STORE_SUBSYSTEM, ChildApplicationContextFactory.class)).thenReturn(cryptodocContentStore);
when(parentContext.containsBean(NEW_STORE_SUBSYSTEM)).thenReturn(true);
when(parentContext.getBean(NEW_STORE_SUBSYSTEM)).thenReturn(newContentStore);
when(parentContext.getBean(NEW_STORE_SUBSYSTEM, ChildApplicationContextFactory.class)).thenReturn(newContentStore);
when(parentContext.containsBean(UNKNOWN_STORE_SUBSYSTEM)).thenReturn(false);
}
private void initSwitchableContext(String sourceBeanName)
{
switchableContext.setSourceBeanName(sourceBeanName);
switchableContext.setPropertyDefaults(new Properties());
switchableContext.setUnencryptedContentStoreBeanName(UNENCRYPTED_STORE_SUBSYSTEM);
switchableContext.setRegistry(propertyBackedBeanRegistry);
switchableContext.setApplicationContext(parentContext);
switchableContext.init();
}
@Test
public void sourceBeanIsUpdateableWhenCurrentStoreIsUnencrypted()
public void canSwitchFromUnencryptedToUnencrypted()
{
initSwitchableContext(UNENCRYPTED_STORE_SUBSYSTEM);
boolean updateable = switchableContext.isUpdateable(SOURCE_BEAN_NAME_PROPERTY);
assertTrue("It should be possible to switch subsystems when the current store is unencrypted.", updateable);
switchableContext.setProperty(SOURCE_BEAN_NAME_PROPERTY, NEW_UNENCRYPTED_STORE_SUBSYSTEM);
assertEquals(NEW_UNENCRYPTED_STORE_SUBSYSTEM, switchableContext.getProperty(SOURCE_BEAN_NAME_PROPERTY));
}
@Test
public void canSetSourceBeanWhenCurrentStoreIsUnencrypted()
public void canSwitchFromUnencryptedToEncrypted_NoLicenseInfo()
{
initSwitchableContext(UNENCRYPTED_STORE_SUBSYSTEM);
switchableContext.setProperty(SOURCE_BEAN_NAME_PROPERTY, ENCRYPTED_STORE_SUBSYSTEM);
assertEquals(ENCRYPTED_STORE_SUBSYSTEM, switchableContext.getProperty(SOURCE_BEAN_NAME_PROPERTY));
}
@Test
public void sourceBeanIsNotUpdatableWhenCurrentStoreIsEncrypted()
public void canSwitchFromUnencryptedToEncrypted_Supported()
{
initSwitchableContext(UNENCRYPTED_STORE_SUBSYSTEM);
DescriptorServiceAvailableEvent event = new DescriptorServiceAvailableEvent(descriptorService);
when(descriptorService.getLicenseDescriptor()).thenReturn(licenseDescriptor);
when(licenseDescriptor.isCryptodocEnabled()).thenReturn(true);
switchableContext.onApplicationEvent(event);
switchableContext.setProperty(SOURCE_BEAN_NAME_PROPERTY, ENCRYPTED_STORE_SUBSYSTEM);
assertEquals(ENCRYPTED_STORE_SUBSYSTEM, switchableContext.getProperty(SOURCE_BEAN_NAME_PROPERTY));
}
@Test
public void canSwitchFromEncryptedToEncrypted_NoLicenseInfo()
{
initSwitchableContext(ENCRYPTED_STORE_SUBSYSTEM);
boolean updateable = switchableContext.isUpdateable(SOURCE_BEAN_NAME_PROPERTY);
assertFalse("It should not be possible to switch subsystems when the current store is encrypted.", updateable);
when(newContentStore.isEncryptedContent()).thenReturn(true);
switchableContext.setProperty(SOURCE_BEAN_NAME_PROPERTY, NEW_STORE_SUBSYSTEM);
assertEquals(NEW_STORE_SUBSYSTEM, switchableContext.getProperty(SOURCE_BEAN_NAME_PROPERTY));
}
@Test
public void canSwitchFromNewEncryptedToEncrypted_NoLicenseInfo()
{
initSwitchableContext(NEW_STORE_SUBSYSTEM);
when(newContentStore.isEncryptedContent()).thenReturn(true);
switchableContext.setProperty(SOURCE_BEAN_NAME_PROPERTY, ENCRYPTED_STORE_SUBSYSTEM);
assertEquals(ENCRYPTED_STORE_SUBSYSTEM, switchableContext.getProperty(SOURCE_BEAN_NAME_PROPERTY));
}
@Test
public void cannotSwitchFromUnencryptedToEncrypted_NotSupported()
{
initSwitchableContext(UNENCRYPTED_STORE_SUBSYSTEM);
DescriptorServiceAvailableEvent event = new DescriptorServiceAvailableEvent(descriptorService);
when(descriptorService.getLicenseDescriptor()).thenReturn(licenseDescriptor);
when(licenseDescriptor.isCryptodocEnabled()).thenReturn(false);
switchableContext.onApplicationEvent(event);
try
{
switchableContext.setProperty(SOURCE_BEAN_NAME_PROPERTY, ENCRYPTED_STORE_SUBSYSTEM);
fail("It shouldn't be possible to switch to an encrypted content store when the license doesn't support it.");
}
catch (IllegalStateException e)
{
// expected
}
// The content store didn't change
assertEquals(UNENCRYPTED_STORE_SUBSYSTEM, switchableContext.getProperty(SOURCE_BEAN_NAME_PROPERTY));
}
@Test
public void cannotSetSourceBeanWhenCurrentStoreIsEncrypted()
public void cannotSwitchFromEncryptedToUnencrypted()
{
initSwitchableContext(ENCRYPTED_STORE_SUBSYSTEM);
try
@@ -108,9 +184,45 @@ public class CryptodocSwitchableApplicationContextFactoryTest
}
catch (IllegalStateException e)
{
// Good
// expected
}
// The content store didn't change
assertEquals(ENCRYPTED_STORE_SUBSYSTEM, switchableContext.getProperty(SOURCE_BEAN_NAME_PROPERTY));
}
@Test
public void cannotSwitchFromEncryptedToNewUnencrypted()
{
initSwitchableContext(ENCRYPTED_STORE_SUBSYSTEM);
when(newContentStore.isEncryptedContent()).thenReturn(false);
try
{
switchableContext.setProperty(SOURCE_BEAN_NAME_PROPERTY, NEW_STORE_SUBSYSTEM);
fail("It shouldn't be possible to switch to an unencrypted content store from an encrypted one.");
}
catch (IllegalStateException e)
{
// expected
}
// The content store didn't change
assertEquals(ENCRYPTED_STORE_SUBSYSTEM, switchableContext.getProperty(SOURCE_BEAN_NAME_PROPERTY));
}
@Test
public void sourceBeanIsNotUpdatableToUnknownBean()
{
initSwitchableContext(UNENCRYPTED_STORE_SUBSYSTEM);
try
{
switchableContext.setProperty(SOURCE_BEAN_NAME_PROPERTY, UNKNOWN_STORE_SUBSYSTEM);
fail("It shouldn't be possible to set the sourceBean to an unknown one.");
}
catch (IllegalStateException e)
{
// expected
}
// The content store didn't change
assertEquals(UNENCRYPTED_STORE_SUBSYSTEM, switchableContext.getProperty(SOURCE_BEAN_NAME_PROPERTY));
}
}