1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.setup.for4_5;
35
36 import static java.lang.String.format;
37 import static org.apache.commons.lang.ArrayUtils.contains;
38 import info.magnolia.cms.core.Content;
39 import info.magnolia.cms.core.HierarchyManager;
40 import info.magnolia.cms.core.MgnlNodeType;
41 import info.magnolia.cms.core.NodeData;
42 import info.magnolia.cms.filters.FilterManager;
43 import info.magnolia.cms.util.ContentUtil;
44 import info.magnolia.module.InstallContext;
45 import info.magnolia.module.delta.AbstractRepositoryTask;
46 import info.magnolia.module.delta.TaskExecutionException;
47
48 import java.util.Collection;
49 import java.util.Set;
50
51 import javax.jcr.RepositoryException;
52
53 import org.apache.commons.lang.ArrayUtils;
54 import org.apache.commons.lang.StringUtils;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 import com.google.common.base.Function;
59 import com.google.common.base.Predicates;
60 import com.google.common.collect.Iterables;
61 import com.google.common.collect.Sets;
62
63
64
65
66
67
68 public class UpdateSecurityFilterClientCallbacksConfiguration extends AbstractRepositoryTask {
69
70 private static final Logger log = LoggerFactory.getLogger(UpdateSecurityFilterClientCallbacksConfiguration.class);
71
72 private static final String FORM_CLASS = "info.magnolia.cms.security.auth.callback.FormClientCallback";
73 private static final String COMPOSITE_CLASS = "info.magnolia.cms.security.auth.callback.CompositeCallback";
74 private static final String BASIC_CLASS = "info.magnolia.cms.security.auth.callback.BasicClientCallback";
75 private static final String REDIRECT_CLASS = "info.magnolia.cms.security.auth.callback.RedirectClientCallback";
76 private static final String[] SIMPLE_CALLBACK_CLASSES = new String[]{FORM_CLASS, BASIC_CLASS, REDIRECT_CLASS};
77 private final String fromFilterName;
78 private final String targetFilterName;
79 private boolean wereWeAbleToUpdateEverything = true;
80
81 public UpdateSecurityFilterClientCallbacksConfiguration(String fromFilterName, String targetFilterName) {
82 super("Security filter configuration", "Moves the client callback configuration from the " + fromFilterName + " to the new " + targetFilterName + " filter to enable multiple client callbacks.");
83 this.fromFilterName = fromFilterName;
84 this.targetFilterName = targetFilterName;
85 }
86
87 @Override
88 protected void doExecute(InstallContext ctx) throws RepositoryException, TaskExecutionException {
89 final HierarchyManager hm = ctx.getConfigHierarchyManager();
90 final Content fromFilterNode = hm.getContent(FilterManager.SERVER_FILTERS + "/" + fromFilterName);
91 final Content targetFilterNode = hm.getContent(FilterManager.SERVER_FILTERS + "/" + targetFilterName);
92
93 final Content newCallbacksNode = targetFilterNode.createContent("clientCallbacks", MgnlNodeType.NT_CONTENTNODE);
94 final Content currentCallbackNode = fromFilterNode.getContent("clientCallback");
95 final String currentClass = currentCallbackNode.getNodeData("class").getString();
96 if (contains(SIMPLE_CALLBACK_CLASSES, currentClass)) {
97 addCallback(ctx, newCallbacksNode, null, currentCallbackNode, null);
98 } else if (!currentCallbackNode.hasChildren()) {
99
100 addCallback(ctx, newCallbacksNode, "custom", currentCallbackNode, null);
101 } else if (COMPOSITE_CLASS.equals(currentClass)) {
102 final Collection<Content> existingCallbacks = currentCallbackNode.getContent("patterns").getChildren();
103 for (Content existingCallback : existingCallbacks) {
104 final String clazz = existingCallback.getNodeData("class").getString();
105 if ("info.magnolia.cms.util.UrlPatternDelegate".equals(clazz)) {
106 final String url = existingCallback.getNodeData("url").getString();
107 addCallback(ctx, newCallbacksNode, existingCallback.getName(), existingCallback.getContent("delegate"), url);
108 } else {
109 ctx.warn("Unknown callback class at " + existingCallback.getHandle() + ":" + clazz);
110 wereWeAbleToUpdateEverything = false;
111 }
112 }
113 } else {
114 ctx.warn("Unknown callback class:" + currentClass);
115 wereWeAbleToUpdateEverything = false;
116 }
117
118
119 if (wereWeAbleToUpdateEverything) {
120 currentCallbackNode.delete();
121 } else {
122 ContentUtil.moveInSession(currentCallbackNode, fromFilterNode.getHandle() + "/_clientCallback_backup_config");
123 ctx.warn(format(
124 "Client callback configuration for %s was not standard: an untouched copy of %s has been kept at %s. Please check, validate and correct the new configuration at %s.",
125 fromFilterName, fromFilterNode.getHandle() + "/clientCallback", fromFilterNode.getHandle() + "/_clientCallback_backup_config", newCallbacksNode.getHandle()
126 ));
127
128 }
129 }
130
131 private void addCallback(InstallContext ctx, Content target, String givenCallbackName, Content source, String urlPattern) throws RepositoryException {
132 final String clazz = source.getNodeData("class").getString();
133 final String newName;
134 if (givenCallbackName == null && contains(SIMPLE_CALLBACK_CLASSES, clazz)) {
135 newName = simplifyClassName(clazz);
136 } else if (givenCallbackName != null) {
137 newName = givenCallbackName;
138 } else {
139 log.warn("Can not determine name for callback at " + source.getHandle());
140 wereWeAbleToUpdateEverything = false;
141 return;
142 }
143
144 final Content newCallback = target.createContent(newName, MgnlNodeType.NT_CONTENTNODE);
145 copyStringProperty(source, newCallback, "class");
146 if (FORM_CLASS.equals(clazz)) {
147 copyStringProperty(source, newCallback, "loginForm");
148 logAndIgnore(ctx, source, "realmName");
149 checkRemainingProperties(ctx, source, "class", "loginForm", "realmName");
150 } else if (REDIRECT_CLASS.equals(clazz)) {
151 copyStringProperty(source, newCallback, "location");
152 logAndIgnore(ctx, source, "realmName");
153 checkRemainingProperties(ctx, source, "class", "location", "realmName");
154 } else if (BASIC_CLASS.equals(clazz)) {
155 copyStringProperty(source, newCallback, "realmName");
156 checkRemainingProperties(ctx, source, "class", "realmName");
157 } else {
158 log.warn("Unknown callback class: " + clazz + "; copying all properties.");
159 wereWeAbleToUpdateEverything = false;
160 copyRemainingProperties(ctx, source, newCallback, "class");
161 }
162
163 if (urlPattern != null) {
164 Content urlPatternContent = newCallback.createContent("urlPattern", MgnlNodeType.NT_CONTENTNODE);
165 urlPatternContent.setNodeData("class", "info.magnolia.cms.util.SimpleUrlPattern");
166 urlPatternContent.setNodeData("patternString", urlPattern);
167 }
168
169 }
170
171 private String simplifyClassName(String clazz) {
172 return StringUtils.removeEnd(StringUtils.substringAfterLast(clazz, "."), "ClientCallback").toLowerCase();
173 }
174
175 private void copyStringProperty(Content source, Content target, String propertyName) throws RepositoryException {
176 target.setNodeData(propertyName, source.getNodeData(propertyName).getString());
177 }
178
179
180
181
182 private void logAndIgnore(InstallContext ctx, Content source, String propertyName) throws RepositoryException {
183 if (source.hasNodeData(propertyName)) {
184 ctx.warn(source.getHandle() + " has a '" + propertyName + "' property; it is ignored and has been removed.");
185 }
186 }
187
188
189
190
191 private void checkRemainingProperties(InstallContext ctx, Content source, String... ignoredProperties) {
192 final Set<String> ignoredPropsSet = Sets.newHashSet(ignoredProperties);
193 final Collection<NodeData> allProps = source.getNodeDataCollection();
194 final Iterable<String> allPropsNames = Iterables.transform(allProps, new Function<NodeData, String>() {
195 @Override
196 public String apply(NodeData from) {
197 return from.getName();
198 }
199 });
200 final Iterable<String> remaining = Iterables.filter(allPropsNames, Predicates.not(Predicates.in(ignoredPropsSet)));
201 if (!Iterables.isEmpty(remaining)) {
202 log.warn(source.getHandle() + " has the following unknown properties which were not copied: " + remaining);
203 wereWeAbleToUpdateEverything = false;
204 }
205 }
206
207 private void copyRemainingProperties(InstallContext ctx, Content source, Content target, String... ignoredProperties) throws RepositoryException {
208 final Collection<NodeData> existingProps = source.getNodeDataCollection();
209 for (NodeData prop : existingProps) {
210 if (ArrayUtils.contains(ignoredProperties, prop.getName())) {
211 continue;
212 }
213 copyStringProperty(source, target, prop.getName());
214 }
215 }
216
217 }