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 jakarta.servlet.Filter;
024import jakarta.servlet.FilterChain;
025import jakarta.servlet.FilterConfig;
026import jakarta.servlet.ServletException;
027import jakarta.servlet.ServletRequest;
028import jakarta.servlet.ServletResponse;
029import jakarta.servlet.RequestDispatcher;
030import jakarta.servlet.http.HttpServletResponse;
031import jakarta.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        @Override       // Filter
136        public void doFilter( final ServletRequest request,
137                                                        final ServletResponse response,
138                                                        final FilterChain chain ) throws IOException, ServletException {
139
140                final HttpServletRequest req = (HttpServletRequest)request ;
141                req.setCharacterEncoding( "UTF-8" );    // 5.3.0.0 (2010/12/01)
142
143                if( isValidAccess( req ) ) {
144                        final String h_r = req.getParameter( REQ_KEY );
145                        // ハッシュ化キーが存在する。
146                        // 6.0.2.5 (2014/10/31) refactoring: findBugs:未チェック/未確認のキャスト対応。
147                        if( h_r != null && response instanceof HttpServletResponse ) {
148                                final HttpServletResponse resp = ((HttpServletResponse)response);
149                                final String qu = URLHashMap.getValue( h_r );
150                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
151                                // キーに対する実アドレスが存在しない。(行き先無しのケース)
152                                if( qu == null ) {
153                                        final String url = resp.encodeRedirectURL( initPage );
154                                        resp.sendRedirect( url );
155                                }
156                                // キーに対する実アドレスが存在する。
157                                else {
158                                        final String requestURI = req.getRequestURI();          // /gf/jsp/index.jsp など
159                                        final String cntxPath   = req.getContextPath();         // /gf など
160                                        // 自分自身のコンテキストと同じなので、forward できる。
161                                        if( requestURI.startsWith( cntxPath ) ) {
162                                                final String url = requestURI.substring(cntxPath.length()) + "?" + qu ;
163                                                final RequestDispatcher rd = request.getRequestDispatcher( url );
164                                                rd.forward( request,response );
165                                        }
166                                        // そうでない場合、リダイレクトする。
167                                        else {
168                                                final String url = resp.encodeRedirectURL( requestURI + "?" + qu );
169                                                resp.sendRedirect( url );
170                                        }
171                                }
172                        }
173                        // ハッシュ化キーが存在しない。
174                        else {
175                                chain.doFilter(request, response);
176                        }
177                }
178                else {
179                        // アクセス拒否を示すメッセージファイルの内容を出力する。
180                        response.setContentType( "text/html; charset=UTF-8" );
181                        final PrintWriter out = response.getWriter();
182                        out.println( refuseMsg() );                                                     // 6.3.8.3 (2015/10/03)
183                        out.flush();
184                }
185        }
186
187        /**
188         * フィルターの初期処理メソッドです。
189         *
190         * フィルターに対してweb.xml で初期パラメータを設定します。
191         *   ・filename   :停止時メッセージ表示ファイル名
192         *   ・initPage   :最初にアクセスされる初期画面アドレス(初期値:/jsp/index.jsp)
193         *   ・debug      :デバッグメッセージの表示(初期値:false)
194         *
195         * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。
196         * @og.rev 6.2.4.1 (2015/05/22) REAL_PATH 対応。realPath は、HybsSystem経由で、取得する。
197         * @og.rev 6.3.8.3 (2015/10/03) filenameの初期値設定。
198         *
199         * @param config FilterConfigオブジェクト
200         */
201        @Override       // Filter
202        public void init( final FilterConfig config ) {
203                initPage = StringUtil.nval( config.getInitParameter("initPage"), initPage );
204                isDebug  = StringUtil.nval( config.getInitParameter("debug")   , isDebug  );
205
206                filename = HybsSystem.getRealPath() + StringUtil.nval( config.getInitParameter("filename") , filename );        // 6.3.8.3 (2015/10/03)
207        }
208
209        /**
210         * フィルターの終了処理メソッドです。
211         *
212         */
213        @Override       // Filter
214        public void destroy() {
215                // ここでは処理を行いません。
216        }
217
218        /**
219         * アクセス拒否を示すメッセージ内容。
220         *
221         * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。
222         * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
223         * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
224         *
225         * @return アクセス拒否を示すメッセージファイルの内容
226         */
227        private String refuseMsg() {
228                // アクセス拒否を示すメッセージファイルの内容を管理する FileString オブジェクトを構築する。
229
230                // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
231                return FileUtil.getValue( filename , HybsConst.UTF_8 );                 // 6.4.5.2 (2016/05/06)
232        }
233
234        /**
235         * フィルターの内部状態をチェックするメソッドです。
236         *
237         * 判定条件は、URL_ACCESS_SECURITY_LEVEL 変数 に応じて異なります。
238         *     レベル0:なにも制限はありません。
239         *     レベル1:Referer チェックを行います。つまり、URLを直接入力しても動作しません。
240         *     レベル2:URLのハッシュ化/暗号化処理を、外部URLに対してのみ行います。(チェックは、レベル1と同等)
241         *     レベル3:URLのパラメータがハッシュ化/暗号化されている必要があります。
242         *     それ以外:アクセスを停止します。
243         *
244         * @param request HttpServletRequestオブジェクト
245         *
246         * @return      (true:許可  false:拒否)
247         */
248        private boolean isValidAccess( final HttpServletRequest request ) {
249                if( ACCS_LVL == 0 )      { return true;  }      // レベル0:無条件アクセス
250
251                final String httpReferer = request.getHeader( "Referer" );
252                final String requestURI  = request.getRequestURI();
253                final String queryString = request.getQueryString();
254                final String hashVal     = request.getParameter( REQ_KEY );
255
256                if( isDebug ) {
257                        System.out.println( "URLHashFilter#httpReferer = " + httpReferer );
258                        System.out.println( "URLHashFilter#requestURI  = " + requestURI  );
259                }
260
261                // 基準となる許可:パラメータなし or ハッシュありの場合
262                final boolean flag2 = queryString == null || hashVal != null ;
263
264                // レベル1,2:パラメータなし or ハッシュあり or Refererあり の場合、許可
265                if( ACCS_LVL == 1 || ACCS_LVL == 2 ) {
266                        return flag2 || httpReferer != null ;
267                }
268
269                // レベル3:パラメータなし or ハッシュありの場合、許可
270                if( ACCS_LVL == 3 ) {
271                        final String cntxPath = request.getContextPath();               // /gf など
272                        // 特別処置
273                        return flag2 ||
274                                          requestURI.equalsIgnoreCase( initPage )            ||
275                                          requestURI.startsWith( cntxPath + "/jsp/menu/"   ) ||
276                                          requestURI.startsWith( cntxPath + "/jsp/custom/" ) ||
277                                          requestURI.startsWith( cntxPath + "/jsp/common/" ) ;
278                }
279
280                return false;   // それ以外:無条件拒否
281        }
282
283        /**
284         * 内部状態を文字列で返します。
285         *
286         * @return      このクラスの文字列表示
287         * @og.rtnNotNull
288         */
289        @Override       // Object
290        public String toString() {
291                final StringBuilder sb = new StringBuilder( BUFFER_MIDDLE )
292                        .append( this.getClass().getCanonicalName() ).append( " : ")
293                        .append( "initPage = [" ).append( initPage ).append( "] , ")
294                        .append( "isDebug  = [" ).append( isDebug  ).append( "]");
295                return sb.toString();
296        }
297}