/**
* This file Copyright (c) 2011-2012 Magnolia International
* Ltd. (http://www.magnolia-cms.com). All rights reserved.
*
*
* This file is dual-licensed under both the Magnolia
* Network Agreement and the GNU General Public License.
* You may elect to use one or the other of these licenses.
*
* This file is distributed in the hope that it will be
* useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
* Redistribution, except as permitted by whichever of the GPL
* or MNA you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or
* modify this file under the terms of the GNU General
* Public License, Version 3, as published by the Free Software
* Foundation. You should have received a copy of the GNU
* General Public License, Version 3 along with this program;
* if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 2. For the Magnolia Network Agreement (MNA), this file
* and the accompanying materials are made available under the
* terms of the MNA which accompanies this distribution, and
* is available at http://www.magnolia-cms.com/mna.html
*
* Any modifications to this file must keep this entire header
* intact.
*
*/
package info.magnolia.setup.for4_5;
import info.magnolia.cms.core.Content;
import info.magnolia.cms.core.HierarchyManager;
import info.magnolia.cms.core.NodeData;
import info.magnolia.context.MgnlContext;
import info.magnolia.importexport.PropertiesImportExport;
import info.magnolia.module.InstallContext;
import info.magnolia.module.InstallContextImpl;
import info.magnolia.module.ModuleRegistry;
import info.magnolia.module.model.ModuleDefinition;
import info.magnolia.module.model.Version;
import info.magnolia.test.RepositoryTestCase;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.jcr.RepositoryException;
import org.apache.commons.lang.StringUtils;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static info.magnolia.test.hamcrest.ContentMatchers.hasContent;
import static info.magnolia.test.hamcrest.ContentMatchers.hasNodeData;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.contains;
import static org.mockito.Mockito.*;
/**
* @author gjoseph
* @version $Revision: $ ($Author: $)
*/
public class UpdateSecurityFilterClientCallbacksConfigurationTest extends RepositoryTestCase {
private final String OLD_FILTER_PATH = "/server/filters/dummySecurityFilter";
private final String NEW_FILTER_PATH = "/server/filters/securityCallback";
private InstallContextImpl ctx;
private ModuleRegistry moduleRegistry;
@Before
public void before() throws Exception {
moduleRegistry = mock(ModuleRegistry.class);
ctx = spy(new InstallContextImpl(moduleRegistry));
ctx.setCurrentModule(new ModuleDefinition("test-module", Version.parseVersion("0"), null, null));
}
@After
public void after() throws Exception {
verifyZeroInteractions(moduleRegistry);
// ComponentsTestUtil.clear();
// SystemProperty.clear();
// MgnlContext.setInstance(null);
// Components.setComponentProvider(null);
}
private static final String[] NEW_FILTER_PRE_CREATED = new String[]{
"/server/filters/securityCallback@type=mgnl:content",
"/server/filters/securityCallback.class=info.magnolia.cms.security.SecurityCallbackFilter",
"/server/filters/securityCallback/bypasses@type=mgnl:contentNode",
"/server/filters/securityCallback/bypasses.dummy=prout",
};
private static final String[] STANDARD_DEFAULT_CONFIG = new String[]{
"/server/filters/dummySecurityFilter@type=mgnl:content",
"/server/filters/dummySecurityFilter.class=info.magnolia.but.seriously.this.filter.class.should.not.be.needed.for.the.test",
"/server/filters/dummySecurityFilter/clientCallback@type=mgnl:contentNode",
"/server/filters/dummySecurityFilter/clientCallback.class=info.magnolia.cms.security.auth.callback.FormClientCallback",
"/server/filters/dummySecurityFilter/clientCallback.loginForm=/mgnl-resources/loginForm/login.html",
"/server/filters/dummySecurityFilter/clientCallback.realmName=Magnolia"
};
@Test
public void rearrangementOfBasicDefaultConfig() throws Exception {
final HierarchyManager hm = setupHM(STANDARD_DEFAULT_CONFIG);
when(ctx.getConfigHierarchyManager()).thenReturn(hm);
final UpdateSecurityFilterClientCallbacksConfiguration task = new UpdateSecurityFilterClientCallbacksConfiguration("dummySecurityFilter", "securityCallback");
task.execute(ctx);
final Content oldFilterNode = hm.getContent(OLD_FILTER_PATH);
assertThat(oldFilterNode, allOf(
not(hasContent("clientCallback")),
not(hasContent("clientCallbacks"))
));
final Content newFilterNode = hm.getContent(NEW_FILTER_PATH);
assertThat(newFilterNode, allOf(
not(hasContent("clientCallback")),
hasContent("clientCallbacks", "mgnl:contentNode")
));
final Content callbacks = newFilterNode.getContent("clientCallbacks");
assertThat(callbacks.getChildren(), hasSize(1));
assertThat(callbacks.getNodeDataCollection(), Matchers.<NodeData>empty());
assertThat(callbacks, hasContent("form", "mgnl:contentNode"));
final Content formCallback = callbacks.getContent("form");
// TODO empty() matcher doesn't play well with generics - see http://code.google.com/p/hamcrest/issues/detail?id=97
assertThat(formCallback.getChildren(), Matchers.<Content>empty());
// TODO - no Hamcrest matcher could combine those, but we should be able to write something that combines it.
// hasProperties() or hasOnlyTheseProperties(name/value pairs) - see org.hamcrest.beans.SamePropertyValuesAs.PropertyMatcher
assertThat(formCallback.getNodeDataCollection(), hasSize(2));
assertThat(formCallback, allOf(
hasNodeData("class", "info.magnolia.cms.security.auth.callback.FormClientCallback"),
hasNodeData("loginForm", "/mgnl-resources/loginForm/login.html"),
// also check that we did not copy the unnecessary realmName property
not(hasNodeData("realmName", "Magnolia"))
));
assertThat(ctx.getMessages().size(), equalTo(1));
assertThat(ctx.getMessages(), allOf(
hasEntry(equalTo("test-module (version 0.0.0)"), allOf(
hasSize(1),
contains(
allOf(
notNullValue(),
hasProperty("priority", is(InstallContext.MessagePriority.warning)),
hasProperty("message", is("/server/filters/dummySecurityFilter/clientCallback has a 'realmName' property; it is ignored and has been removed."))
)
)
))
));
}
private static final String[] STANDARD_DELEGATING_CONFIG = new String[]{
"/server/filters/dummySecurityFilter@type=mgnl:content",
"/server/filters/dummySecurityFilter.class=info.magnolia.but.seriously.this.filter.class.should.not.be.needed.for.the.test",
"/server/filters/dummySecurityFilter/clientCallback@type=mgnl:contentNode",
"/server/filters/dummySecurityFilter/clientCallback.class=info.magnolia.cms.security.auth.callback.CompositeCallback",
"/server/filters/dummySecurityFilter/clientCallback/patterns/public.class=info.magnolia.cms.util.UrlPatternDelegate",
"/server/filters/dummySecurityFilter/clientCallback/patterns/public.url=/demo-project/members-area/protected*",
"/server/filters/dummySecurityFilter/clientCallback/patterns/public/delegate.class=info.magnolia.cms.security.auth.callback.RedirectClientCallback",
"/server/filters/dummySecurityFilter/clientCallback/patterns/public/delegate.location=/demo-project/members-area/login.html",
"/server/filters/dummySecurityFilter/clientCallback/patterns/public/delegate.realmName=Magnolia",
"/server/filters/dummySecurityFilter/clientCallback/patterns/magnolia.class=info.magnolia.cms.util.UrlPatternDelegate",
"/server/filters/dummySecurityFilter/clientCallback/patterns/magnolia.url=*",
"/server/filters/dummySecurityFilter/clientCallback/patterns/magnolia/delegate.class=info.magnolia.cms.security.auth.callback.FormClientCallback",
"/server/filters/dummySecurityFilter/clientCallback/patterns/magnolia/delegate.loginForm=/mgnl-resources/loginForm/login.html",
"/server/filters/dummySecurityFilter/clientCallback/patterns/magnolia/delegate.realmName=Magnolia"
};
@Test
public void rearrangementOfSTKsDefaultConfig() throws Exception {
final HierarchyManager hm = setupHM(STANDARD_DELEGATING_CONFIG);
when(ctx.getConfigHierarchyManager()).thenReturn(hm);
final UpdateSecurityFilterClientCallbacksConfiguration task = new UpdateSecurityFilterClientCallbacksConfiguration("dummySecurityFilter", "securityCallback");
task.execute(ctx);
final Content oldFilterNode = hm.getContent(OLD_FILTER_PATH);
assertThat(oldFilterNode, allOf(
not(hasContent("clientCallback")),
not(hasContent("clientCallbacks"))
));
final Content newFilterNode = hm.getContent(NEW_FILTER_PATH);
assertThat(newFilterNode, allOf(
not(hasContent("clientCallback")),
hasContent("clientCallbacks", "mgnl:contentNode")
));
final Content callbacks = newFilterNode.getContent("clientCallbacks");
assertThat(callbacks.getChildren(), hasSize(2));
assertThat(callbacks.getNodeDataCollection(), Matchers.<NodeData>empty());
assertThat(callbacks, allOf(
hasContent("public", "mgnl:contentNode"),
hasContent("magnolia", "mgnl:contentNode"),
not(hasContent("form"))
));
final Content publicCallback = callbacks.getContent("public");
final Content magnoliaCallback = callbacks.getContent("magnolia");
assertThat(publicCallback.getChildren(), hasSize(1));
assertThat(magnoliaCallback.getChildren(), hasSize(1));
assertThat(publicCallback.getNodeDataCollection(), hasSize(2));
assertThat(publicCallback, allOf(
hasNodeData("class", "info.magnolia.cms.security.auth.callback.RedirectClientCallback"),
hasNodeData("location", "/demo-project/members-area/login.html")
));
assertThat(magnoliaCallback.getNodeDataCollection(), hasSize(2));
assertThat(magnoliaCallback, allOf(
hasNodeData("class", "info.magnolia.cms.security.auth.callback.FormClientCallback"),
hasNodeData("loginForm", "/mgnl-resources/loginForm/login.html")
));
assertThat(ctx.getMessages().size(), equalTo(1));
assertThat(ctx.getMessages(), allOf(
hasEntry(equalTo("test-module (version 0.0.0)"), allOf(
hasSize(2),
contains(
allOf(
notNullValue(),
hasProperty("priority", is(InstallContext.MessagePriority.warning)),
hasProperty("message", is("/server/filters/dummySecurityFilter/clientCallback/patterns/public/delegate has a 'realmName' property; it is ignored and has been removed."))
),
allOf(
notNullValue(),
hasProperty("priority", is(InstallContext.MessagePriority.warning)),
hasProperty("message", is("/server/filters/dummySecurityFilter/clientCallback/patterns/magnolia/delegate has a 'realmName' property; it is ignored and has been removed."))
)
)
))
));
}
private static final String[] NON_STANDARD_CONFIG = new String[]{
"/server/filters/dummySecurityFilter@type=mgnl:content",
"/server/filters/dummySecurityFilter.class=info.magnolia.but.seriously.this.filter.class.should.not.be.needed.for.the.test",
"/server/filters/dummySecurityFilter/clientCallback@type=mgnl:contentNode",
"/server/filters/dummySecurityFilter/clientCallback/class=org.acme.CustomClientCallbackClass",
"/server/filters/dummySecurityFilter/clientCallback/loginForm=/mgnl-resources/loginForm/login.html",
"/server/filters/dummySecurityFilter/clientCallback/someCustomProperty=arbitrary value"
};
@Test
public void simpleCustomCallbackMovedAndBackedUpWithWarning() throws Exception {
final HierarchyManager hm = setupHM(NON_STANDARD_CONFIG);
when(ctx.getConfigHierarchyManager()).thenReturn(hm);
final UpdateSecurityFilterClientCallbacksConfiguration task = new UpdateSecurityFilterClientCallbacksConfiguration("dummySecurityFilter", "securityCallback");
task.execute(ctx);
final Content oldFilterNode = hm.getContent(OLD_FILTER_PATH);
assertThat(oldFilterNode, allOf(
not(hasContent("clientCallback")),
not(hasContent("clientCallbacks")),
hasContent("_clientCallback_backup_config")
));
final Content newFilterNode = hm.getContent(NEW_FILTER_PATH);
assertThat(newFilterNode, allOf(
not(hasContent("clientCallback")),
hasContent("clientCallbacks", "mgnl:contentNode")
));
final Content backup = oldFilterNode.getContent("_clientCallback_backup_config");
assertThat(backup.getChildren(), hasSize(0));
assertThat(backup.getNodeDataCollection(), hasSize(3));
assertThat(backup, allOf(
hasNodeData("class", "org.acme.CustomClientCallbackClass"),
hasNodeData("loginForm", "/mgnl-resources/loginForm/login.html"),
hasNodeData("someCustomProperty", "arbitrary value")
));
final Content callbacks = newFilterNode.getContent("clientCallbacks");
assertThat(callbacks.getChildren(), hasSize(1));
assertThat(callbacks.getNodeDataCollection(), Matchers.<NodeData>empty());
assertThat(callbacks, hasContent("custom", "mgnl:contentNode"));
final Content customCallback = callbacks.getContent("custom");
assertThat(customCallback.getChildren(), Matchers.<Content>empty());
assertThat(customCallback.getNodeDataCollection(), hasSize(3));
assertThat(customCallback, allOf(
hasNodeData("class", "org.acme.CustomClientCallbackClass"),
hasNodeData("loginForm", "/mgnl-resources/loginForm/login.html"),
hasNodeData("someCustomProperty", "arbitrary value")
));
assertThat(ctx.getMessages().size(), equalTo(1));
assertThat(ctx.getMessages(), allOf(
hasEntry(equalTo("test-module (version 0.0.0)"), allOf(
hasSize(1),
contains(
allOf(
notNullValue(),
hasProperty("priority", is(InstallContext.MessagePriority.warning)),
hasProperty("message", is("Client callback configuration for dummySecurityFilter was not standard: an untouched copy of /server/filters/dummySecurityFilter/clientCallback has been kept at /server/filters/dummySecurityFilter/_clientCallback_backup_config. Please check, validate and correct the new configuration at /server/filters/securityCallback/clientCallbacks."))
)
)
))
));
}
private static final String[] NON_STANDARD_DELEGATING_CONFIG = new String[]{
"/server/filters/dummySecurityFilter@type=mgnl:content",
"/server/filters/dummySecurityFilter.class=info.magnolia.but.seriously.this.filter.class.should.not.be.needed.for.the.test",
"/server/filters/dummySecurityFilter/clientCallback@type=mgnl:contentNode",
"/server/filters/dummySecurityFilter/clientCallback.class=info.magnolia.cms.security.auth.callback.CompositeCallback",
"/server/filters/dummySecurityFilter/clientCallback/patterns/custom1.class=org.foobar.CustomDelegate",
"/server/filters/dummySecurityFilter/clientCallback/patterns/custom1.url=/path1/*",
"/server/filters/dummySecurityFilter/clientCallback/patterns/custom1/delegate.class=org.foobar.CustomCallback1",
"/server/filters/dummySecurityFilter/clientCallback/patterns/custom1/delegate.location=/demo-project/members-area/login.html",
"/server/filters/dummySecurityFilter/clientCallback/patterns/custom1/delegate.lala=lolo",
"/server/filters/dummySecurityFilter/clientCallback/patterns/custom2.class=info.magnolia.cms.util.UrlPatternDelegate",
"/server/filters/dummySecurityFilter/clientCallback/patterns/custom2.url=/path2/*",
"/server/filters/dummySecurityFilter/clientCallback/patterns/custom2/delegate.class=org.foobar.CustomCallback2",
"/server/filters/dummySecurityFilter/clientCallback/patterns/custom2/delegate.dings=bums"
};
@Test
public void nonStandardCompositeConfigIsBackedUp() throws Exception {
final HierarchyManager hm = setupHM(NON_STANDARD_DELEGATING_CONFIG);
when(ctx.getConfigHierarchyManager()).thenReturn(hm);
final UpdateSecurityFilterClientCallbacksConfiguration task = new UpdateSecurityFilterClientCallbacksConfiguration("dummySecurityFilter", "securityCallback");
task.execute(ctx);
final Content oldFilterNode = hm.getContent(OLD_FILTER_PATH);
assertThat(oldFilterNode, allOf(
not(hasContent("clientCallback")),
not(hasContent("clientCallbacks")),
hasContent("_clientCallback_backup_config")
));
final Content newFilterNode = hm.getContent(NEW_FILTER_PATH);
assertThat(newFilterNode, allOf(
not(hasContent("clientCallback")),
hasContent("clientCallbacks", "mgnl:contentNode")
));
final Content backup = oldFilterNode.getContent("_clientCallback_backup_config");
assertThat(backup.getChildren(), hasSize(1));
assertThat(backup.getNodeDataCollection(), hasSize(1));
assertThat(backup, allOf(
hasNodeData("class", "info.magnolia.cms.security.auth.callback.CompositeCallback"),
hasContent("patterns", "mgnl:contentNode"),
hasContent("patterns/custom1", "mgnl:contentNode")
// too lazy to assert the whole thing was backed up...
));
final Content callbacks = newFilterNode.getContent("clientCallbacks");
assertThat(callbacks.getChildren(), hasSize(1));
assertThat(callbacks.getNodeDataCollection(), Matchers.<NodeData>empty());
assertThat("we can't migrate custom1, it doesn't have a known delegator class", callbacks, not(hasContent("custom1", "mgnl:contentNode")));
assertThat(callbacks, hasContent("custom2", "mgnl:contentNode"));
final Content customCallback2 = callbacks.getContent("custom2");
assertThat(customCallback2.getNodeDataCollection(), hasSize(2));
assertThat(customCallback2, allOf(
hasNodeData("class", "org.foobar.CustomCallback2"),
hasNodeData("dings", "bums")
));
assertThat(ctx.getMessages().size(), equalTo(1));
assertThat(ctx.getMessages(), allOf(
hasEntry(equalTo("test-module (version 0.0.0)"),
// no need to check for size, contains() ensures each matcher matches, in order
contains(
allOf(
notNullValue(),
hasProperty("priority", is(InstallContext.MessagePriority.warning)),
hasProperty("message", is("Unknown callback class at /server/filters/dummySecurityFilter/clientCallback/patterns/custom1:org.foobar.CustomDelegate"))
),
allOf(
notNullValue(),
hasProperty("priority", is(InstallContext.MessagePriority.warning)),
hasProperty("message", is("Client callback configuration for dummySecurityFilter was not standard: an untouched copy of /server/filters/dummySecurityFilter/clientCallback has been kept at /server/filters/dummySecurityFilter/_clientCallback_backup_config. Please check, validate and correct the new configuration at /server/filters/securityCallback/clientCallbacks."))
)
)
)
));
}
private HierarchyManager setupHM(String... lines) throws IOException, RepositoryException {
// TODO can't use MockHierarchyManager, as it doesn't support the move method used by this task - which is why we're currently extending RepositoryTestCase ...
// return MockUtil.createAndSetHierarchyManager("config", StringUtils.join(lines, '\n'));
final HierarchyManager hm = MgnlContext.getHierarchyManager("config");
final String newFilterPreCreated = StringUtils.join(NEW_FILTER_PRE_CREATED, '\n');
final String testData = StringUtils.join(lines, '\n');
final ByteArrayInputStream props = new ByteArrayInputStream((newFilterPreCreated + testData).getBytes());
new PropertiesImportExport().createContent(hm.getRoot(), props);
return hm;
}
}