001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.csp; 018 019import org.apache.wicket.request.IRequestHandler; 020import org.apache.wicket.request.IRequestHandlerDelegate; 021import org.apache.wicket.request.cycle.IRequestCycleListener; 022import org.apache.wicket.request.cycle.RequestCycle; 023import org.apache.wicket.request.http.WebResponse; 024 025/** 026 * An {@link IRequestCycleListener} that adds {@code Content-Security-Policy} and/or 027 * {@code Content-Security-Policy-Report-Only} headers based on the supplied configuration. 028 * 029 * @author Sven Haster 030 * @author Emond Papegaaij 031 */ 032public class CSPRequestCycleListener implements IRequestCycleListener 033{ 034 private final ContentSecurityPolicySettings settings; 035 036 public CSPRequestCycleListener(ContentSecurityPolicySettings settings) 037 { 038 this.settings = settings; 039 } 040 041 @Override 042 public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler) 043 { 044 // WICKET-7028- this is needed for redirect to buffer use case. 045 protect(cycle, handler); 046 } 047 048 @Override 049 public void onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler) 050 { 051 protect(cycle, handler); 052 } 053 054 protected void protect(RequestCycle cycle, IRequestHandler handler) 055 { 056 if (!mustProtect(handler) || !(cycle.getResponse() instanceof WebResponse)) 057 { 058 return; 059 } 060 061 WebResponse webResponse = (WebResponse)cycle.getResponse(); 062 if (!webResponse.isHeaderSupported()) 063 { 064 return; 065 } 066 067 settings.getConfiguration().entrySet().stream().filter(entry -> entry.getValue().isSet()) 068 .forEach(entry -> { 069 CSPHeaderMode mode = entry.getKey(); 070 CSPHeaderConfiguration config = entry.getValue(); 071 String headerValue = config.renderHeaderValue(settings, cycle); 072 webResponse.setHeader(mode.getHeader(), headerValue); 073 if (config.isAddLegacyHeaders()) 074 { 075 webResponse.setHeader(mode.getLegacyHeader(), headerValue); 076 } 077 }); 078 } 079 080 /** 081 * Must the given handler be protected. 082 * 083 * @param handler 084 * handler 085 * @return <code>true</code> if must be protected 086 * @see ContentSecurityPolicySettings#mustProtectRequest(IRequestHandler) 087 */ 088 protected boolean mustProtect(IRequestHandler handler) 089 { 090 if (handler instanceof IRequestHandlerDelegate) 091 { 092 return mustProtect(((IRequestHandlerDelegate)handler).getDelegateHandler()); 093 } 094 095 return settings.mustProtectRequest(handler); 096 } 097 098}