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