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