001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.hayabusa.filter;
017
018import org.opengion.hayabusa.common.HybsSystem;
019
020import java.io.IOException;
021import java.io.PrintWriter;
022
023import javax.servlet.Filter;
024import javax.servlet.FilterChain;
025import javax.servlet.FilterConfig;
026import javax.servlet.ServletException;
027import javax.servlet.ServletRequest;
028import javax.servlet.ServletResponse;
029import javax.servlet.RequestDispatcher;
030import javax.servlet.http.HttpServletResponse;
031import javax.servlet.http.HttpServletRequest;
032
033import org.opengion.fukurou.security.URLHashMap;
034import org.opengion.fukurou.util.StringUtil;
035import org.opengion.fukurou.util.FileUtil;                                                              // 6.4.5.2 (2016/05/06)
036import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;              // 6.1.0.0 (2014/12/26) refactoring
037import org.opengion.fukurou.system.HybsConst;                                                   // 6.4.5.2 (2016/05/06)
038
039/**
040 * URLHashFilter は、Filter インターフェースを継承した URLチェッククラスです。
041 * web.xml で filter 設定することにより、処理を開始します。
042 * filter 処理は、設定レベルとURLの飛び先により処理方法が異なります。
043 * このフィルターでは、ハッシュ化/暗号化ではなく、アドレスに戻す作業になります。
044 * 内部URLの場合はハッシュ化、外部URLの場合は暗号化に適用されます。
045 *
046 * 基本的には、外部へのURLでエンジンシステムへ飛ばす場合は、暗号化になります。
047 * 内部へのURLは、基本的に、パラメータのみ暗号化を行います。なお、直接画面IDを
048 * 指定して飛ばす場合を、止めるかどうかは、設定レベルに依存します。
049 *
050 * フィルターの設定レベルは、システムリソースの URL_ACCESS_SECURITY_LEVEL 変数で
051 * 設定します。
052 * なお、各レベル共通で、戻し処理はレベルに関係なく実行されます。
053 *   レベル0:なにも制限はありません。
054 *   レベル1:Referer チェックを行います。つまり、URLを直接入力しても動作しません。
055 *             ただし、Refererが付いてさえいれば、アクセス許可を与えます。
056 *             Referer 無しの場合でも、URLにパラメータが存在しない、または、
057 *             アドレスがハッシュ化/暗号化されている場合は、アクセスを許可します。
058 *             レベル1の場合、ハッシュ戻し/復号化処理は行います。あくまで、ハッシュ化
059 *             暗号化されていない場合でも、Refererさえあれば、許可するということです。
060 *             (パラメータなし or ハッシュあり or Refererあり の場合、許可)
061 *   レベル2:フィルター処理としては、レベル1と同じです。
062 *             異なるのは、URLのハッシュ化/暗号化処理を、外部URLに対してのみ行います。
063 *             (パラメータなし or ハッシュあり or Refererあり の場合、許可)
064 *   レベル3:URLのパラメータがハッシュ化/暗号化されている必要があります。
065 *             レベル1同様、URLにパラメータが存在しない場合は、アクセスを許可します。
066 *             レベル1と異なるのは、パラメータは必ずハッシュ化か、暗号化されている
067 *             必要があるということです。(内部/外部問わず)
068 *             (パラメータなし or ハッシュあり の場合、許可)
069 *   それ以外:アクセスを停止します。
070 *
071 * フィルターに対してweb.xml でパラメータを設定します。
072 *   ・filename   :停止時メッセージ表示ファイル名(例:/jsp/custom/refuseAccess.html)
073 *   ・initPage   :最初にアクセスされる初期画面アドレス(初期値:/jsp/index.jsp)
074 *   ・debug      :デバッグメッセージの表示(初期値:false)
075 *
076 * 【WEB-INF/web.xml】
077 *     <filter>
078 *         <filter-name>URLHashFilter</filter-name>
079 *         <filter-class>org.opengion.hayabusa.filter.URLHashFilter</filter-class>
080 *         <init-param>
081 *             <param-name>filename</param-name>
082 *             <param-value>/jsp/custom/refuseAccess.html</param-value>
083 *         </init-param>
084 *          <init-param>
085 *              <param-name>initPage</param-name>
086 *              <param-value>/jsp/index.jsp</param-value>
087 *          </init-param>
088 *          <init-param>
089 *              <param-name>debug</param-name>
090 *              <param-value>false</param-value>
091 *          </init-param>
092 *     </filter>
093 *
094 *     <filter-mapping>
095 *         <filter-name>URLHashFilter</filter-name>
096 *         <url-pattern>*.jsp</url-pattern>
097 *     </filter-mapping>
098 *
099 * @og.group フィルター処理
100 *
101 * @og.rev 5.2.2.0 (2010/11/01) 新規追加
102 *
103 * @version  5.2.2.0 (2010/11/01)
104 * @author   Kazuhiko Hasegawa
105 * @since    JDK1.6,
106 */
107public final class URLHashFilter implements Filter {
108        private static final String REQ_KEY = HybsSystem.URL_HASH_REQ_KEY ;
109
110        private static final int ACCS_LVL = HybsSystem.sysInt( "URL_ACCESS_SECURITY_LEVEL" );
111
112        private String          initPage        = "/jsp/index.jsp";
113        private String          filename        = "jsp/custom/refuseAccess.html" ;                      // 6.3.8.3 (2015/10/03) アクセス拒否時メッセージ表示ファイル名
114        private boolean         isDebug         ;
115
116        /**
117         * デフォルトコンストラクター
118         *
119         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
120         */
121        public URLHashFilter() { super(); }             // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
122
123        /**
124         * フィルター処理本体のメソッドです。
125         *
126         * @og.rev 5.3.0.0 (2010/12/01) 文字化け対策として、setCharacterEncoding を実行する。
127         * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。
128         *
129         * @param       request         ServletRequestオブジェクト
130         * @param       response        ServletResponseオブジェクト
131         * @param       chain           FilterChainオブジェクト
132         * @throws IOException 入出力エラーが発生したとき
133         * @throws ServletException サーブレット関係のエラーが発生した場合、throw されます。
134         */
135        public void doFilter( final ServletRequest request,
136                                                        final ServletResponse response,
137                                                        final FilterChain chain ) throws IOException, ServletException {
138
139                final HttpServletRequest req = (HttpServletRequest)request ;
140                req.setCharacterEncoding( "UTF-8" );    // 5.3.0.0 (2010/12/01)
141
142                if( isValidAccess( req ) ) {
143                        final String h_r = req.getParameter( REQ_KEY );
144                        // ハッシュ化キーが存在する。
145                        // 6.0.2.5 (2014/10/31) refactoring: findBugs:未チェック/未確認のキャスト対応。
146                        if( h_r != null && response instanceof HttpServletResponse ) {
147                                final HttpServletResponse resp = ((HttpServletResponse)response);
148                                final String qu = URLHashMap.getValue( h_r );
149                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
150                                // キーに対する実アドレスが存在しない。(行き先無しのケース)
151                                if( qu == null ) {
152                                        final String url = resp.encodeRedirectURL( initPage );
153                                        resp.sendRedirect( url );
154                                }
155                                // キーに対する実アドレスが存在する。
156                                else {
157                                        final String requestURI = req.getRequestURI();          // /gf/jsp/index.jsp など
158                                        final String cntxPath   = req.getContextPath();         // /gf など
159                                        // 自分自身のコンテキストと同じなので、forward できる。
160                                        if( requestURI.startsWith( cntxPath ) ) {
161                                                final String url = requestURI.substring(cntxPath.length()) + "?" + qu ;
162                                                final RequestDispatcher rd = request.getRequestDispatcher( url );
163                                                rd.forward( request,response );
164                                        }
165                                        // そうでない場合、リダイレクトする。
166                                        else {
167                                                final String url = resp.encodeRedirectURL( requestURI + "?" + qu );
168                                                resp.sendRedirect( url );
169                                        }
170                                }
171                        }
172                        // ハッシュ化キーが存在しない。
173                        else {
174                                chain.doFilter(request, response);
175                        }
176                }
177                else {
178                        // アクセス拒否を示すメッセージファイルの内容を出力する。
179                        response.setContentType( "text/html; charset=UTF-8" );
180                        final PrintWriter out = response.getWriter();
181                        out.println( refuseMsg() );                                                     // 6.3.8.3 (2015/10/03)
182                        out.flush();
183                }
184        }
185
186        /**
187         * フィルターの初期処理メソッドです。
188         *
189         * フィルターに対してweb.xml で初期パラメータを設定します。
190         *   ・filename   :停止時メッセージ表示ファイル名
191         *   ・initPage   :最初にアクセスされる初期画面アドレス(初期値:/jsp/index.jsp)
192         *   ・debug      :デバッグメッセージの表示(初期値:false)
193         *
194         * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。
195         * @og.rev 6.2.4.1 (2015/05/22) REAL_PATH 対応。realPath は、HybsSystem経由で、取得する。
196         * @og.rev 6.3.8.3 (2015/10/03) filenameの初期値設定。
197         *
198         * @param config FilterConfigオブジェクト
199         */
200        public void init( final FilterConfig config ) {
201                initPage = StringUtil.nval( config.getInitParameter("initPage"), initPage );
202                isDebug  = StringUtil.nval( config.getInitParameter("debug")   , isDebug  );
203
204                filename = HybsSystem.getRealPath() + StringUtil.nval( config.getInitParameter("filename") , filename );        // 6.3.8.3 (2015/10/03)
205        }
206
207        /**
208         * フィルターの終了処理メソッドです。
209         *
210         */
211        public void destroy() {
212                // ここでは処理を行いません。
213        }
214
215        /**
216         * アクセス拒否を示すメッセージ内容。
217         *
218         * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。
219         * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
220         * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
221         *
222         * @return アクセス拒否を示すメッセージファイルの内容
223         */
224        private String refuseMsg() {
225                // アクセス拒否を示すメッセージファイルの内容を管理する FileString オブジェクトを構築する。
226
227                // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
228                return FileUtil.getValue( filename , HybsConst.UTF_8 );                 // 6.4.5.2 (2016/05/06)
229        }
230
231        /**
232         * フィルターの内部状態をチェックするメソッドです。
233         *
234         * 判定条件は、URL_ACCESS_SECURITY_LEVEL 変数 に応じて異なります。
235         *     レベル0:なにも制限はありません。
236         *     レベル1:Referer チェックを行います。つまり、URLを直接入力しても動作しません。
237         *     レベル2:URLのハッシュ化/暗号化処理を、外部URLに対してのみ行います。(チェックは、レベル1と同等)
238         *     レベル3:URLのパラメータがハッシュ化/暗号化されている必要があります。
239         *     それ以外:アクセスを停止します。
240         *
241         * @param request HttpServletRequestオブジェクト
242         *
243         * @return      (true:許可  false:拒否)
244         */
245        private boolean isValidAccess( final HttpServletRequest request ) {
246                if( ACCS_LVL == 0 )      { return true;  }      // レベル0:無条件アクセス
247
248                final String httpReferer = request.getHeader( "Referer" );
249                final String requestURI  = request.getRequestURI();
250                final String queryString = request.getQueryString();
251                final String hashVal     = request.getParameter( REQ_KEY );
252
253                if( isDebug ) {
254                        System.out.println( "URLHashFilter#httpReferer = " + httpReferer );
255                        System.out.println( "URLHashFilter#requestURI  = " + requestURI  );
256                }
257
258                // 基準となる許可:パラメータなし or ハッシュありの場合
259                final boolean flag2 = queryString == null || hashVal != null ;
260
261                // レベル1,2:パラメータなし or ハッシュあり or Refererあり の場合、許可
262                if( ACCS_LVL == 1 || ACCS_LVL == 2 ) {
263                        return flag2 || httpReferer != null ;
264                }
265
266                // レベル3:パラメータなし or ハッシュありの場合、許可
267                if( ACCS_LVL == 3 ) {
268                        final String cntxPath = request.getContextPath();               // /gf など
269                        // 特別処置
270                        return flag2 ||
271                                          requestURI.equalsIgnoreCase( initPage )            ||
272                                          requestURI.startsWith( cntxPath + "/jsp/menu/"   ) ||
273                                          requestURI.startsWith( cntxPath + "/jsp/custom/" ) ||
274                                          requestURI.startsWith( cntxPath + "/jsp/common/" ) ;
275                }
276
277                return false;   // それ以外:無条件拒否
278        }
279
280        /**
281         * 内部状態を文字列で返します。
282         *
283         * @return      このクラスの文字列表示
284         * @og.rtnNotNull
285         */
286        @Override
287        public String toString() {
288                final StringBuilder sb = new StringBuilder( BUFFER_MIDDLE )
289                        .append( this.getClass().getCanonicalName() ).append( " : ")
290                        .append( "initPage = [" ).append( initPage ).append( "] , ")
291                        .append( "isDebug  = [" ).append( isDebug  ).append( "]");
292                return sb.toString();
293        }
294}