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.File;                                                    // 5.7.3.2 (2014/02/28) Tomcat8 対応
019import java.io.BufferedReader;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStreamReader;
023import java.io.PrintWriter;
024import java.io.UnsupportedEncodingException;
025
026import javax.servlet.Filter;
027import javax.servlet.FilterChain;
028import javax.servlet.FilterConfig;
029import javax.servlet.ServletContext;
030import javax.servlet.ServletException;
031import javax.servlet.ServletRequest;
032import javax.servlet.ServletResponse;
033import javax.servlet.http.HttpServletRequest;
034
035import org.opengion.fukurou.security.HybsCryptography;
036import org.opengion.fukurou.util.Closer;
037import org.opengion.fukurou.util.StringUtil;
038import org.opengion.hayabusa.common.HybsSystem;
039
040/**
041 * URLCheckFilter は、Filter インターフェースを継承した URLチェッククラスです。
042 * web.xml で filter 設定することにより、該当のリソースに対して、og:linkタグで、
043 * useURLCheck="true"が指定されたリンクURL以外を拒否することができます。
044 * また、og:linkタグを経由した場合でも、リンクの有効期限を設定することで、
045 * リンクURLの漏洩に対しても、一定時間の経過を持って、アクセスを拒否することができます。
046 * また、リンク時にユーザー情報も埋め込んでいますので(初期値は、ログインユーザー)、
047 * リンクアドレスが他のユーザーに知られた場合でも、アクセスを拒否することができます。
048 * 
049 * システムリソースの「URL_CHECK_CRYPT」で暗号復号化のキーを指定可能です。
050 * 指定しない場合はデフォルトのキーが利用されます。
051 * キーの形式はHybsCryptographyに従います。
052 *
053 * フィルターに対してweb.xml でパラメータを設定します。
054 *   ・filename :停止時メッセージ表示ファイル名
055 *   ・ignoreURL:暗号化されたURLのうち空白に置き換える接頭文字列を指定します。
056 *                      外部からアクセスしたURLがロードバランサで内部向けURLに変換されてチェックが動作しないような場合に
057 *                      利用します。https://wwwX.のように指定します。通常は設定しません。
058 *
059 * 【WEB-INF/web.xml】
060 *     <filter>
061 *         <filter-name>URLCheckFilter</filter-name>
062 *         <filter-class>org.opengion.hayabusa.filter.URLCheckFilter</filter-class>
063 *         <init-param>
064 *             <param-name>filename</param-name>
065 *             <param-value>jsp/custom/refuseAccess.html</param-value>
066 *         </init-param>
067 *     </filter>
068 *
069 *     <filter-mapping>
070 *         <filter-name>URLCheckFilter</filter-name>
071 *         <url-pattern>/jsp/*</url-pattern>
072 *     </filter-mapping>
073 *
074 * @og.group フィルター処理
075 *
076 * @version  4.0
077 * @author   Hiroki Nakamura
078 * @since    JDK5.0,
079 */
080public final class URLCheckFilter implements Filter {
081
082//      private static final HybsCryptography HYBS_CRYPTOGRAPHY = new HybsCryptography(); // 4.3.7.0 (2009/06/01)
083        private static final HybsCryptography HYBS_CRYPTOGRAPHY 
084                                                = new HybsCryptography( HybsSystem.sys( "URL_CHECK_CRYPT" ) ); // 5.8.8.0 (2015/06/05)
085
086        private String  filename  = null;                       // アクセス拒否時メッセージ表示ファイル名
087//      private int             maxInterval = 3600;                     // リンクの有効期限
088        private boolean  isDebug         = false;
089        private boolean  isDecode        = true;                // 5.4.5.0(2012/02/28) URIDecodeするかどうか
090        
091        private String          ignoreURL       = null; //5.8.6.1 (2015/04/17) 飛んできたcheckURLから取り除くURL文字列
092        private String          ommitURL        = null; // 5.10.11.0 (2019/05/03) URLチェックを行わないURLの正規表現
093        private String          ommitReferer    = null; // 5.10.11.0 (2019/05/03) URLチェックを行わないドメイン
094        
095        private String encoding = "utf-8";      // 5.10.12.4 (2019/06/21) 日本語対応
096
097        /**
098         * フィルター処理本体のメソッドです。
099         * 
100         * @og.rev 5.10.12.4 (2019/06/21) 日本語対応(encoding指定)
101         * @og.rev 5.10.16.1 (2019/10/11) デバッグ追加
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, final ServletResponse response, final FilterChain chain ) throws IOException, ServletException {
109                request.setCharacterEncoding(encoding); // 5.10.12.1 (2019/06/21)
110                
111                if( !isValidAccess( request ) ) {
112                        if( isDebug ) {
113                                System.out.println( "  check NG... " ); // 5.10.16.1 
114                        }
115                        BufferedReader in = null ;
116                        try {
117                                response.setContentType( "text/html; charset=UTF-8" );
118                                PrintWriter out = response.getWriter();
119                                in = new BufferedReader( new InputStreamReader(
120                                                                new FileInputStream( filename ) ,"UTF-8" ) );
121                                String str ;
122                                while( (str = in.readLine()) != null ) {
123                                        out.println( str );
124                                }
125                                out.flush();
126                        }
127                        catch( UnsupportedEncodingException ex ) {
128                                String errMsg = "指定されたエンコーディングがサポートされていません。[UTF-8]" ;
129                                throw new RuntimeException( errMsg,ex );
130                        }
131                        catch( IOException ex ) {
132                                String errMsg = "ストリームがオープン出来ませんでした。[" + filename + "]" ;
133                                throw new RuntimeException( errMsg,ex );
134                        }
135                        finally {
136                                Closer.ioClose( in );
137                        }
138                        return;
139                }
140                
141                request.setAttribute( "RequestEncoding", encoding ); // 5.10.12.1 (2019/06/21) リクエスト変数で送信しておく
142
143                chain.doFilter(request, response);
144        }
145
146        /**
147         * フィルターの初期処理メソッドです。
148         *
149         * フィルターに対してweb.xml で初期パラメータを設定します。
150         *   ・maxInterval:リンクの有効期限
151         *   ・filename   :停止時メッセージ表示ファイル名
152         *   ・decode     :URLデコードを行ってチェックするか(初期true)
153         *
154         * @og.rev 5.4.5.0 (2102/02/28)
155         * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。
156         * @og.rev 5.8.6.1 (2015/04/17) DMZのURL変換対応
157         * @og.rev 5.10.11.0 (2019/05/03) ommitURL,ommitReferer
158         * @og.rev 5.10.12.4 (2019/06/21) encoding
159         *
160         * @param filterConfig FilterConfigオブジェクト
161         */
162        public void init(final FilterConfig filterConfig) {
163                ServletContext context = filterConfig.getServletContext();
164//              String realPath = context.getRealPath( "/" );
165                String realPath = context.getRealPath( "" ) + File.separator;           // 5.7.3.2 (2014/02/28) Tomcat8 対応
166
167//              maxInterval = StringUtil.nval( filterConfig.getInitParameter("maxInterval"), maxInterval );
168                filename  = realPath + filterConfig.getInitParameter("filename");
169                isDebug = StringUtil.nval( filterConfig.getInitParameter("debug"), false );
170                isDecode = StringUtil.nval( filterConfig.getInitParameter("decode"), true ); // 5.4.5.0(2012/02/28)
171                ignoreURL = filterConfig.getInitParameter("ignoreURL"); // 5.8.6.1 (2015/04/17)
172                ommitURL = filterConfig.getInitParameter("ommitURL"); // 5.10.11.0 (2019/05/03) 
173                ommitReferer = filterConfig.getInitParameter("ommitReferer"); // 5.10.11.0 (2019/05/03) 
174                encoding = StringUtil.nval( filterConfig.getInitParameter("encoding"), encoding ); // 5.10.12.4 (2019/06/21)
175        }
176
177        /**
178         * フィルターの終了処理メソッドです。
179         *
180         */
181        public void destroy() {
182                // ここでは処理を行いません。
183        }
184
185        /**
186         * フィルターの内部状態をチェックするメソッドです。
187         *
188         * @og.rev 5.4.5.0 (2012/02/28) Decode
189         * @og.rev 5.8.8.2 (2015/07/17) マルチバイト対応追加
190         * @or.rev 5.10.16 (2019/10/11) デバッグ追加
191         *
192         * @param request ServletRequestオブジェクト
193         *
194         * @return      (true:許可  false:拒否)
195         */
196        private boolean isValidAccess( final ServletRequest request ) {
197                String checkKey = request.getParameter( HybsSystem.URL_CHECK_KEY );
198                // 5.10.11.0 (2019/05/03) データ取得位置変更
199                String queryStr = ((HttpServletRequest)request).getQueryString();
200                String reqStr =  ((HttpServletRequest)request).getRequestURL().toString();
201                String referer = ((HttpServletRequest)request).getHeader("REFERER");
202                
203                // 5.10.11.0 referer判定追加
204                // 入っている場合はtrueにする。
205                if(referer != null && ommitReferer != null && referer.indexOf( ommitReferer ) >= 0 ) {
206                        if( isDebug ) {
207                                System.out.println("URLCheck ommitRef"+reqStr);
208                        }
209                        return true;
210                }
211                
212                // リクエスト変数をURLに追加
213                reqStr = reqStr + (queryStr != null ? "?" + queryStr : "");
214                
215                // 5.10.11.0 ommitURL追加
216                // ommitに合致する場合はtrueにする。
217                if(ommitURL != null && reqStr.matches( ommitURL )) {
218                        if( isDebug ) {
219                                System.out.println("URLCheck ommitURL"+reqStr);
220                        }
221                        return true;
222                }
223                
224                if( checkKey == null || checkKey.length() == 0 ) {
225                        if( isDebug ) {
226                                System.out.println( "  check NG [ No Check Key ] = " + reqStr ); // 5.10.16.1 (2019/10/11) reqStr追加
227                        }
228                        return false;
229                }
230
231                boolean rtn = false;
232                try {
233                        checkKey = HYBS_CRYPTOGRAPHY.decrypt( checkKey ).replace( "&", "&" );
234
235                        if( isDebug ) {
236                                System.out.println( "checkKey=" + checkKey );
237                        }
238
239                        String url = checkKey.substring( 0 , checkKey.lastIndexOf( ",time=") );
240                        long time = Long.parseLong( checkKey.substring( checkKey.lastIndexOf( ",time=") + 6, checkKey.lastIndexOf( ",userid=" ) ) );
241                        String userid = checkKey.substring( checkKey.lastIndexOf( ",userid=") + 8 );
242                        // 4.3.8.0 (2009/08/01)
243                        String[] userArr = StringUtil.csv2Array( userid );
244                        
245                        // 5.8.6.1 (2015/04/17)ignoreURL対応
246                        if( ignoreURL!=null && ignoreURL.length()>0 && url.indexOf( ignoreURL ) == 0 ){
247                                url = url.substring( ignoreURL.length() );
248                        }
249
250                        if( isDebug ) {
251                                System.out.println( " [ignoreURL]=" + ignoreURL ); // 2015/04/17 (2015/04/17)
252                                System.out.println( " [url]    =" + url );
253                                System.out.println( " [vtime]  =" + time );
254                                System.out.println( " [userid] =" + userid );
255                        }
256
257                        
258                        // 5.4.5.0 (2012/02/28) URLDecodeを行う
259                        if(isDecode){
260                                if( isDebug ) {
261                                        System.out.println( "[BeforeURIDecode]="+reqStr );
262                                }
263                                reqStr = StringUtil.urlDecode( reqStr );
264                                url = StringUtil.urlDecode( url ); // 5.8.8.2 (2015/07/17)
265                        }
266                        reqStr = reqStr.substring( 0, reqStr.lastIndexOf( HybsSystem.URL_CHECK_KEY ) -1 );
267                        //      String reqStr =  ((HttpServletRequest)request).getRequestURL().toString();
268                        String reqUser = ((HttpServletRequest)request).getRemoteUser();
269
270                        if( isDebug ) {
271                                System.out.println( " [reqURL] =" + reqStr );
272                                System.out.println( " [ctime]  =" + System.currentTimeMillis() );
273                                System.out.println( " [reqUser]=" + reqUser );
274                                System.out.println( " endWith=" + reqStr.endsWith( url ) );
275                                System.out.println( " times=" + (System.currentTimeMillis() - time) );
276                                System.out.println( " [userArr.length]=" + userArr.length );
277                        }
278
279                        if( reqStr.endsWith( url )
280//                                      && System.currentTimeMillis() - time < maxInterval * 1000
281                                        && System.currentTimeMillis() - time < 0
282//                                      && userid.equals( reqUser ) ) {
283                                        && userArr != null && userArr.length > 0 ) {
284
285                                // 4.3.8.0 (2009/08/01)
286                                for( int i=0; i<userArr.length; i++ ) {
287                                        if( isDebug ) {
288                                                System.out.println( " [userArr] =" + userArr[i] ); // 5.10.16.1 
289                                        }
290                                        if( "*".equals( userArr[i] ) || reqUser.equals( userArr[i] ) ) {
291                                                rtn = true;
292                                                if( isDebug ) {
293                                                        System.out.println( "  check OK" );
294                                                }
295                                                break;
296                                        }
297                                }
298                        }
299                }
300                catch( RuntimeException ex ) {
301                        if( isDebug ) {
302                                String errMsg = "チェックエラー。 "
303                                                        + " checkKey=" + checkKey
304                                                        + " " + ex.getMessage();                        // 5.1.8.0 (2010/07/01) errMsg 修正
305                                System.out.println( errMsg );
306                                ex.printStackTrace();
307                        }
308                        rtn = false;
309                }
310                return rtn;
311        }
312
313        /**
314         * 内部状態を文字列で返します。
315         *
316         * @return      このクラスの文字列表示
317         */
318        @Override
319        public String toString() {
320                StringBuilder sb = new StringBuilder();
321                sb.append( "UrlCheckFilter" );
322//              sb.append( "[" ).append( maxInterval ).append( "],");
323                sb.append( "[" ).append( filename  ).append( "],");
324                return (sb.toString());
325        }
326}