View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.config;
21  
22  import org.apache.myfaces.tobago.config.TobagoConfig;
23  import org.apache.myfaces.tobago.context.Theme;
24  import org.apache.myfaces.tobago.context.ThemeImpl;
25  import org.apache.myfaces.tobago.exception.TobagoConfigurationException;
26  import org.apache.myfaces.tobago.sanitizer.Sanitizer;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import javax.faces.application.Application;
31  import javax.faces.context.FacesContext;
32  import java.lang.invoke.MethodHandles;
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  
41  /**
42   * <p>
43   * Implementation of the Tobago configuration.
44   * </p>
45   * <p>
46   * All setters must are protected, so EL can't modify this config.
47   * </p>
48   */
49  //@Named("tobagoConfig") // todo
50  //@ApplicationScoped // todo
51  public class TobagoConfigImpl extends TobagoConfig {
52  
53    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
54  
55    private List<Theme> supportedThemes;
56    private List<String> supportedThemeNames;
57    private Theme defaultTheme;
58    private String defaultThemeName;
59    private Map<String, ThemeImpl> availableThemes;
60    private boolean createSessionSecret;
61    private boolean checkSessionSecret;
62    private boolean preventFrameAttacks;
63    private final ContentSecurityPolicy contentSecurityPolicy;
64    private SecurityAnnotation securityAnnotation;
65    private boolean setNosniffHeader;
66    private Map<String, String> defaultValidatorInfo;
67    private Sanitizer sanitizer;
68    private boolean decodeLineFeed;
69    private Map<String, String> mimeTypes;
70  
71    private boolean locked = false;
72  
73    protected TobagoConfigImpl(String fixme) { // CDI workaround fixme
74      supportedThemeNames = new ArrayList<>();
75      supportedThemes = new ArrayList<>();
76      availableThemes = new HashMap<>();
77      createSessionSecret = true;
78      checkSessionSecret = true;
79      preventFrameAttacks = true;
80      setNosniffHeader = true;
81      securityAnnotation = SecurityAnnotation.disable;
82      decodeLineFeed = true;
83      contentSecurityPolicy = new ContentSecurityPolicy(ContentSecurityPolicy.Mode.OFF.getValue());
84      mimeTypes = new HashMap<>();
85    }
86  
87    /**
88     * Lock the configuration, so it cannot be modified any more.
89     */
90    protected void lock() {
91      locked = true;
92      supportedThemes = Collections.unmodifiableList(supportedThemes);
93      for (final Theme theme : supportedThemes) {
94        ((ThemeImpl) theme).lock();
95      }
96      supportedThemeNames = Collections.unmodifiableList(supportedThemeNames);
97      availableThemes = Collections.unmodifiableMap(availableThemes);
98  
99      contentSecurityPolicy.lock();
100 
101     mimeTypes = Collections.unmodifiableMap(mimeTypes);
102   }
103 
104   private void checkUnlocked() throws IllegalStateException {
105     if (locked) {
106       throw new TobagoConfigurationException("The configuration must not be changed after initialization!");
107     }
108   }
109 
110   protected void addSupportedThemeName(final String name) {
111     checkUnlocked();
112     supportedThemeNames.add(name);
113   }
114 
115   // TODO one init method
116   protected void resolveThemes() {
117     checkUnlocked();
118 
119     if (defaultThemeName != null) {
120       defaultTheme = availableThemes.get(defaultThemeName);
121       checkThemeIsAvailable(defaultThemeName, defaultTheme);
122       if (LOG.isDebugEnabled()) {
123         LOG.debug("name = '{}'", defaultThemeName);
124         LOG.debug("defaultTheme = '{}'", defaultTheme);
125       }
126     } else {
127       int deep = 0;
128       for (final Map.Entry<String, ThemeImpl> entry : availableThemes.entrySet()) {
129         final Theme theme = entry.getValue();
130         if (theme.getFallbackList().size() > deep) {
131           defaultTheme = theme;
132           deep = theme.getFallbackList().size();
133         }
134       }
135       if (defaultTheme == null) {
136         final String error = "Did not found any theme! "
137             + "Please ensure you have a tobago-config.xml with a theme-definition in your "
138             + "theme JAR. Please add a theme JAR to your classpath. Usually "
139             + "tobago-theme-standard.jar in WEB-INF/lib";
140         LOG.error(error);
141         throw new TobagoConfigurationException(error);
142       } else {
143         if (LOG.isInfoEnabled()) {
144           LOG.info("Using default Theme {}", defaultTheme.getName());
145         }
146       }
147     }
148     if (!supportedThemeNames.isEmpty()) {
149       for (final String name : supportedThemeNames) {
150         final Theme theme = availableThemes.get(name);
151         checkThemeIsAvailable(name, theme);
152         supportedThemes.add(theme);
153         if (LOG.isDebugEnabled()) {
154           LOG.debug("name = '{}'", name);
155           LOG.debug("supportedThemes.last() = '{}'", supportedThemes.get(supportedThemes.size() - 1));
156         }
157       }
158     }
159   }
160 
161   private void checkThemeIsAvailable(final String name, final Theme theme) {
162     if (theme == null) {
163       final String error = "Theme not found! name: '" + name + "'. "
164           + "Please ensure you have a tobago-config.xml with a theme-definition in your "
165           + "theme JAR. Found the following themes: " + availableThemes.keySet();
166       LOG.error(error);
167       throw new TobagoConfigurationException(error);
168     }
169   }
170 
171   @Override
172   public Theme getTheme(final String name) {
173     if (name == null) {
174       LOG.debug("searching theme: null");
175       return defaultTheme;
176     }
177     if (defaultTheme != null && defaultTheme.getName().equals(name)) {
178       return defaultTheme;
179     }
180     for (final Theme theme : supportedThemes) {
181       if (theme.getName().equals(name)) {
182         return theme;
183       }
184     }
185     LOG.debug("searching theme '{}' not found. Using default: {}", name, defaultTheme);
186     return defaultTheme;
187   }
188 
189   protected void setDefaultThemeName(final String defaultThemeName) {
190     checkUnlocked();
191     this.defaultThemeName = defaultThemeName;
192   }
193 
194   @Override
195   public List<Theme> getSupportedThemes() {
196     return supportedThemes;
197   }
198 
199   @Override
200   public Theme getDefaultTheme() {
201     return defaultTheme;
202   }
203 
204   protected void addAvailableTheme(final ThemeImpl availableTheme) {
205     checkUnlocked();
206     final String name = availableTheme.getName();
207     if (availableThemes.containsKey(name)) {
208       final ThemeImpl base = availableThemes.get(name);
209       availableThemes.put(name, ThemeImpl.merge(base, availableTheme));
210     } else {
211       availableThemes.put(name, availableTheme);
212     }
213   }
214 
215   public Map<String, ThemeImpl> getAvailableThemes() {
216     return availableThemes;
217   }
218 
219   protected synchronized void initDefaultValidatorInfo() {
220     if (defaultValidatorInfo != null) {
221       checkUnlocked();
222     }
223     final FacesContext facesContext = FacesContext.getCurrentInstance();
224     if (facesContext != null) {
225       try {
226         final Application application = facesContext.getApplication();
227         final Map<String, String> map = application.getDefaultValidatorInfo();
228         if (map.size() > 0) {
229           defaultValidatorInfo = Collections.unmodifiableMap(map);
230         } else {
231           defaultValidatorInfo = Collections.emptyMap();
232         }
233       } catch (final Exception e) {
234         LOG.error("Can't initialize default validators (this happens with JBoss GateIn 3.6.0).", e);
235         defaultValidatorInfo = Collections.emptyMap();
236       }
237     }
238   }
239 
240   @Override
241   public boolean isCreateSessionSecret() {
242     return createSessionSecret;
243   }
244 
245   protected void setCreateSessionSecret(final boolean createSessionSecret) {
246     checkUnlocked();
247     this.createSessionSecret = createSessionSecret;
248   }
249 
250   @Override
251   public boolean isCheckSessionSecret() {
252     return checkSessionSecret;
253   }
254 
255   protected void setCheckSessionSecret(final boolean checkSessionSecret) {
256     checkUnlocked();
257     this.checkSessionSecret = checkSessionSecret;
258   }
259 
260 
261   @Override
262   public boolean isPreventFrameAttacks() {
263     return preventFrameAttacks;
264   }
265 
266   protected void setPreventFrameAttacks(final boolean preventFrameAttacks) {
267     checkUnlocked();
268     this.preventFrameAttacks = preventFrameAttacks;
269   }
270 
271   @Override
272   public ContentSecurityPolicy getContentSecurityPolicy() {
273     return contentSecurityPolicy;
274   }
275 
276   @Override
277   public boolean isSetNosniffHeader() {
278     return setNosniffHeader;
279   }
280 
281   protected void setSetNosniffHeader(final boolean setNosniffHeader) {
282     checkUnlocked();
283     this.setNosniffHeader = setNosniffHeader;
284   }
285 
286   @Override
287   public SecurityAnnotation getSecurityAnnotation() {
288     return securityAnnotation;
289   }
290 
291   public void setSecurityAnnotation(final SecurityAnnotation securityAnnotation) {
292     checkUnlocked();
293     this.securityAnnotation = securityAnnotation;
294   }
295 
296   public Map<String, String> getDefaultValidatorInfo() {
297     // TODO: if the startup hasn't found a FacesContext and Application, this may depend on the order of the listeners.
298     if (defaultValidatorInfo == null) {
299       initDefaultValidatorInfo();
300     }
301     return defaultValidatorInfo;
302   }
303 
304   @Override
305   public Sanitizer getSanitizer() {
306     return sanitizer;
307   }
308 
309   protected void setSanitizer(final Sanitizer sanitizer) {
310     checkUnlocked();
311     this.sanitizer = sanitizer;
312   }
313 
314   @Override
315   public boolean isDecodeLineFeed() {
316     return decodeLineFeed;
317   }
318 
319   public void setDecodeLineFeed(final boolean decodeLineFeed) {
320     checkUnlocked();
321     this.decodeLineFeed = decodeLineFeed;
322   }
323 
324   @Override
325   public Map<String, String> getMimeTypes() {
326     return mimeTypes;
327   }
328 
329   @Override
330   public String toString() {
331     final StringBuilder builder = new StringBuilder();
332     builder.append("TobagoConfigImpl{");
333     builder.append("\nsupportedThemes=[");
334     for (final Theme supportedTheme : supportedThemes) {
335       builder.append(supportedTheme.getName());
336       builder.append(", ");
337     }
338     builder.append("], \ndefaultTheme=");
339     builder.append(defaultTheme != null ? defaultTheme.getName() : null);
340     builder.append(", \navailableThemes=");
341     builder.append(availableThemes.keySet());
342     builder.append(", \ncreateSessionSecret=");
343     builder.append(createSessionSecret);
344     builder.append(", \ncheckSessionSecret=");
345     builder.append(checkSessionSecret);
346     builder.append(", \npreventFrameAttacks=");
347     builder.append(preventFrameAttacks);
348     builder.append(", \ncontentSecurityPolicy=");
349     builder.append(contentSecurityPolicy);
350     builder.append(", \nsecurityAnnotation=");
351     builder.append(securityAnnotation);
352     builder.append(", \nsetNosniffHeader=");
353     builder.append(setNosniffHeader);
354     builder.append(", \ndefaultValidatorInfo=");
355     builder.append(defaultValidatorInfo);
356     builder.append(", \nsanitizer=");
357     builder.append(sanitizer);
358     builder.append(", \ndecodeLineFeed=");
359     builder.append(decodeLineFeed);
360     // to see only different (ignore alternative names for the same theme)
361     builder.append(", \nthemes=");
362     final Set<Theme> all = new HashSet<>(availableThemes.values());
363     builder.append(all);
364     builder.append(", \nmimeTypes=");
365     builder.append(mimeTypes);
366     builder.append('}');
367     return builder.toString();
368   }
369 }