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 java.io.IOException;
019import java.io.PrintWriter;
020
021import javax.servlet.Filter;
022import javax.servlet.FilterChain;
023import javax.servlet.FilterConfig;
024import javax.servlet.ServletException;
025import javax.servlet.ServletRequest;
026import javax.servlet.ServletResponse;
027import javax.servlet.http.HttpServletRequest;
028
029import org.opengion.fukurou.security.HybsCryptography;
030import org.opengion.fukurou.util.StringUtil;
031import org.opengion.hayabusa.common.HybsSystem;
032import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;              // 6.1.0.0 (2014/12/26) refactoring
033import org.opengion.fukurou.system.ThrowUtil;                                                   // 6.4.2.0 (2016/01/29)
034import org.opengion.fukurou.system.HybsConst;                                                   // 6.4.5.2 (2016/05/06)
035
036import org.opengion.fukurou.util.FileUtil;                                                              // 6.4.5.2 (2016/05/06)
037
038/**
039 * URLCheckFilter は、Filter インターフェースを継承した URLチェッククラスです。
040 * web.xml で filter 設定することにより、該当のリソースに対して、og:linkタグで、
041 * useURLCheck="true"が指定されたリンクURL以外を拒否することができます。
042 * また、og:linkタグを経由した場合でも、リンクの有効期限を設定することで、
043 * リンクURLの漏洩に対しても、一定時間の経過を持って、アクセスを拒否することができます。
044 * また、リンク時にユーザー情報も埋め込んでいますので(初期値は、ログインユーザー)、
045 * リンクアドレスが他のユーザーに知られた場合でも、アクセスを拒否することができます。
046 * 
047 * システムリソースの「URL_CHECK_CRYPT」で暗号復号化のキーを指定可能です。
048 * 指定しない場合はデフォルトのキーが利用されます。
049 * キーの形式はHybsCryptographyに従います。
050 *
051 * フィルターに対してweb.xml でパラメータを設定します。
052 *   ・filename :停止時メッセージ表示ファイル名
053 *   ・ignoreURL:暗号化されたURLのうち空白に置き換える接頭文字列を指定します。
054 *              外部からアクセスしたURLがロードバランサで内部向けURLに変換されてチェックが動作しないような場合に
055 *              利用します。https://wwwX.のように指定します。通常は設定しません。
056 *
057 * 【WEB-INF/web.xml】
058 *     <filter>
059 *         <filter-name>URLCheckFilter</filter-name>
060 *         <filter-class>org.opengion.hayabusa.filter.URLCheckFilter</filter-class>
061 *         <init-param>
062 *             <param-name>filename</param-name>
063 *             <param-value>jsp/custom/refuseAccess.html</param-value>
064 *         </init-param>
065 *     </filter>
066 *
067 *     <filter-mapping>
068 *         <filter-name>URLCheckFilter</filter-name>
069 *         <url-pattern>/jsp/*</url-pattern>
070 *     </filter-mapping>
071 *
072 * @og.group フィルター処理
073 *
074 * @version  4.0
075 * @author   Hiroki Nakamura
076 * @since    JDK5.0,
077 */
078public final class URLCheckFilter implements Filter {
079
080        private static final HybsCryptography HYBS_CRYPTOGRAPHY 
081                                                = new HybsCryptography( HybsSystem.sys( "URL_CHECK_CRYPT" ) ); // 5.8.8.0 (2015/06/05)
082
083        private String  filename        = "jsp/custom/refuseAccess.html" ;                      // 6.3.8.3 (2015/10/03) アクセス拒否時メッセージ表示ファイル名
084        private boolean isDebug         ;
085        private boolean isDecode        = true;         // 5.4.5.0(2012/02/28) URIDecodeするかどうか
086
087        private String  ignoreURL       ;                       // 5.8.6.1 (2015/04/17) 飛んできたcheckURLから取り除くURL文字列
088
089        /**
090         * デフォルトコンストラクター
091         *
092         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
093         */
094        public URLCheckFilter() { super(); }            // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
095
096        /**
097         * フィルター処理本体のメソッドです。
098         *
099         * @og.rev 6.2.0.0 (2015/02/27) new BufferedReader … を、FileUtil.getBufferedReader … に変更。
100         * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
101         * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。
102         *
103         * @param       request         ServletRequestオブジェクト
104         * @param       response        ServletResponseオブジェクト
105         * @param       chain           FilterChainオブジェクト
106         * @throws ServletException サーブレット関係のエラーが発生した場合、throw されます。
107         */
108        public void doFilter( final ServletRequest request,
109                                                        final ServletResponse response,
110                                                        final FilterChain chain ) throws IOException, ServletException {
111
112                if( !isValidAccess( request ) ) {
113                        response.setContentType( "text/html; charset=UTF-8" );
114                        final PrintWriter out = response.getWriter();
115                        out.println( refuseMsg() );                                                     // 6.3.8.3 (2015/10/03)
116                        out.flush();
117                        return;
118
119                }
120
121                chain.doFilter(request, response);
122        }
123
124        /**
125         * フィルターの初期処理メソッドです。
126         *
127         * フィルターに対してweb.xml で初期パラメータを設定します。
128         *   ・filename  :停止時メッセージ表示ファイル名(初期:jsp/custom/refuseAccess.html)
129         *   ・decode    :URLデコードを行ってチェックするか(初期:true)
130         *   ・ignoreURL :暗号化されたURLのうち空白に置き換える接頭文字列を指定します(初期:null)
131         *   ・debug     :URLデコードを行ってチェックするか(初期:false)
132         *
133         * @og.rev 5.4.5.0 (2102/02/28)
134         * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。
135         * @og.rev 5.8.6.1 (2015/04/17) DMZのURL変換対応
136         * @og.rev 6.2.4.1 (2015/05/22) REAL_PATH 対応。realPath は、HybsSystem経由で、取得する。
137         * @og.rev 6.3.8.3 (2015/10/03) filenameの初期値設定。
138         *
139         * @param filterConfig FilterConfigオブジェクト
140         */
141        public void init(final FilterConfig filterConfig) {
142
143                filename  = HybsSystem.getRealPath() + StringUtil.nval( filterConfig.getInitParameter("filename") , filename ); // 6.3.8.3 (2015/10/03)
144                isDecode  = StringUtil.nval( filterConfig.getInitParameter("decode"), true );   // 5.4.5.0(2012/02/28)
145                ignoreURL = filterConfig.getInitParameter("ignoreURL");                                                 // 5.8.6.1 (2015/04/17)
146                isDebug   = StringUtil.nval( filterConfig.getInitParameter("debug"), false );
147        }
148
149        /**
150         * フィルターの終了処理メソッドです。
151         *
152         */
153        public void destroy() {
154                // ここでは処理を行いません。
155        }
156
157        /**
158         * アクセス拒否を示すメッセージ内容。
159         *
160         * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。
161         * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
162         * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
163         *
164         * @return アクセス拒否を示すメッセージファイルの内容
165         */
166        private String refuseMsg() {
167                // アクセス拒否を示すメッセージファイルの内容を管理する FileString オブジェクトを構築する。
168
169                // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
170                return FileUtil.getValue( filename , HybsConst.UTF_8 );                 // 6.4.5.2 (2016/05/06)
171        }
172
173        /**
174         * フィルターの内部状態をチェックするメソッドです。
175         *
176         * @og.rev 5.4.5.0 (2012/02/28) Decode
177         * @og.rev 5.8.6.1 (2015/04/17) DMZのURL変換対応
178         * @og.rev 5.8.8.2 (2015/07/17) マルチバイト対応追加
179         * @og.rev 6.3.8.4 (2015/10/09) デバッグメッセージの追加
180         * @og.rev 6.4.2.0 (2016/01/29) ex.printStackTrace() を、ThrowUtil#ogStackTrace(Throwable) に置き換え。
181         *
182         * @param request ServletRequestオブジェクト
183         *
184         * @return      (true:許可  false:拒否)
185         */
186        private boolean isValidAccess( final ServletRequest request ) {
187                // 6.3.8.4 (2015/10/09) デバッグメッセージの追加
188                if( isDebug ) {
189                        System.out.println( ((HttpServletRequest)request).getRequestURI() );
190                }
191
192                String checkKey = request.getParameter( HybsSystem.URL_CHECK_KEY );
193                if( checkKey == null || checkKey.isEmpty() ) {
194                        if( isDebug ) {
195                                System.out.println( "  check NG [ No Check Key ]" );
196                        }
197                        return false;
198                }
199
200                boolean rtn = false;
201                try {
202                        checkKey = HYBS_CRYPTOGRAPHY.decrypt( checkKey ).replace( "&", "&" );
203
204                        if( isDebug ) {
205                                System.out.println( "  checkKey=" + checkKey );
206                        }
207
208                        // 5.8.6.1 (2015/04/17) DMZのURL変換対応 (ちょっと整理しておきます)
209
210                        final int tmAd = checkKey.lastIndexOf( ",time=" );
211                        final int usAd = checkKey.lastIndexOf( ",userid=" );
212
213                        String url = checkKey.substring( 0 , tmAd );
214                        final long  time    = Long.parseLong( checkKey.substring( tmAd + 6, usAd ) );
215                        final String userid = checkKey.substring( usAd + 8 );
216
217                        // 4.3.8.0 (2009/08/01)
218                        final String[] userArr = StringUtil.csv2Array( userid );
219
220                        // 5.8.6.1 (2015/04/17)ignoreURL対応
221                        if( ignoreURL != null && ignoreURL.length()>0 && url.indexOf( ignoreURL ) == 0 ){
222                                url = url.substring( ignoreURL.length() );
223                        }
224
225                        if( isDebug ) {
226                                System.out.println( "   [ignoreURL]=" + ignoreURL );    // 2015/04/17 (2015/04/17)
227                                System.out.println( "   [url]      =" + url );
228                                System.out.println( "   [vtime]    =" + time );
229                                System.out.println( "   [userid]   =" + userid );
230                        }
231
232                        String reqStr =  ((HttpServletRequest)request).getRequestURL().toString() + "?" + ((HttpServletRequest)request).getQueryString();
233                        // 5.4.5.0 (2012/02/28) URLDecodeを行う
234                        if( isDecode ){
235                                if( isDebug ) {
236                                        System.out.println( "[BeforeURIDecode]="+reqStr );
237                                }
238                                reqStr = StringUtil.urlDecode( reqStr );
239                                url = StringUtil.urlDecode( url );              // 5.8.8.2 (2015/07/17)
240                        }
241                        reqStr = reqStr.substring( 0, reqStr.lastIndexOf( HybsSystem.URL_CHECK_KEY ) -1 );
242                        //      String reqStr =  ((HttpServletRequest)request).getRequestURL().toString();
243                        final String reqUser = ((HttpServletRequest)request).getRemoteUser();
244
245                        if( isDebug ) {
246                                System.out.println( "   [reqURL] =" + reqStr );
247                                System.out.println( "   [ctime]  =" + System.currentTimeMillis() );
248                                System.out.println( "   [reqUser]=" + reqUser );
249                        }
250
251                        if( reqStr.endsWith( url )
252                                        && System.currentTimeMillis() - time < 0
253                                        && userArr != null && userArr.length > 0 ) {
254                                // 4.3.8.0 (2009/08/01)
255                                for( int i=0; i<userArr.length; i++ ) {
256                                        if( "*".equals( userArr[i] ) || reqUser.equals( userArr[i] ) ) {
257                                                rtn = true;
258                                                if( isDebug ) {
259                                                        System.out.println( "  check OK" );
260                                                }
261                                                break;
262                                        }
263                                }
264                        }
265                }
266                catch( final RuntimeException ex ) {
267                        if( isDebug ) {
268                                final String errMsg = "チェックエラー。 "
269                                                        + " checkKey=" + checkKey
270                                                        + " " + ex.getMessage();                        // 5.1.8.0 (2010/07/01) errMsg 修正
271                                System.out.println( errMsg );
272                                System.err.println( ThrowUtil.ogStackTrace( ex ) );                             // 6.4.2.0 (2016/01/29)
273                        }
274                        rtn = false;
275                }
276                return rtn;
277        }
278
279        /**
280         * 内部状態を文字列で返します。
281         *
282         * @return      このクラスの文字列表示
283         * @og.rtnNotNull
284         */
285        @Override
286        public String toString() {
287                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
288                        .append( "UrlCheckFilter" )
289                        .append( "filename=[" ).append( filename  ).append( "],")
290                        .append( "isDecode=[" ).append( isDecode  ).append( ']');               // 6.0.2.5 (2014/10/31) char を append する。
291                return buf.toString();
292        }
293}