001    package org.apache.myfaces.tobago.webapp;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.apache.commons.codec.binary.Base64;
021    import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
022    import org.apache.myfaces.tobago.renderkit.html.HtmlConstants;
023    
024    import javax.faces.context.FacesContext;
025    import javax.servlet.http.HttpSession;
026    import java.io.IOException;
027    import java.io.Serializable;
028    import java.security.SecureRandom;
029    import java.util.Map;
030    
031    public class Secret implements Serializable {
032    
033      private static final long serialVersionUID = 1L;
034    
035      private static final String KEY = Secret.class.getName();
036    
037      private static final SecureRandom RANDOM = new SecureRandom();
038    
039      private static final int SECRET_LENGTH = 16;
040    
041      private static final boolean COMMONS_CODEC_AVAILABLE = commonsCodecAvailable();
042    
043      private static boolean commonsCodecAvailable() {
044        try {
045          Base64.encodeBase64URLSafeString(new byte[0]);
046          return true;
047        } catch (Error e) {
048          return false;
049        }
050      }
051    
052      private String secret;
053    
054      private Secret() {
055        byte[] bytes = new byte[SECRET_LENGTH];
056        RANDOM.nextBytes(bytes);
057        secret = COMMONS_CODEC_AVAILABLE ? encodeBase64(bytes) : encodeHex(bytes);
058      }
059    
060      private String encodeBase64(byte[] bytes) {
061        return Base64.encodeBase64URLSafeString(bytes);
062      }
063    
064      private String encodeHex(byte[] bytes) {
065        StringBuilder builder = new StringBuilder(SECRET_LENGTH * 2);
066        for (byte b : bytes) {
067          builder.append(String.format("%02x", b));
068        }
069        return builder.toString();
070      }
071    
072      /**
073       * Checks that the request contains a parameter {@link org.apache.myfaces.tobago.webapp.Secret#KEY}
074       * which is equals to a secret value in the session.
075       */
076      public static boolean check(FacesContext facesContext) {
077        Map requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
078        String fromRequest = (String) requestParameterMap.get(Secret.KEY);
079        Map sessionMap = facesContext.getExternalContext().getSessionMap();
080        Secret secret = (Secret) sessionMap.get(Secret.KEY);
081        return secret != null && secret.secret.equals(fromRequest);
082      }
083    
084      /**
085       * Encode a hidden field with the secret value from the session.
086       */
087      public static void encode(FacesContext facesContext, TobagoResponseWriter writer) throws IOException {
088        writer.startElement(HtmlConstants.INPUT, null);
089        writer.writeAttribute(HtmlAttributes.TYPE, "hidden", false);
090        writer.writeAttribute(HtmlAttributes.NAME, Secret.KEY, false);
091        writer.writeAttribute(HtmlAttributes.ID, Secret.KEY, false);
092        Map sessionMap = facesContext.getExternalContext().getSessionMap();
093        Secret secret = (Secret) sessionMap.get(Secret.class.getName());
094        writer.writeAttribute(HtmlAttributes.VALUE, secret.secret, false);
095        writer.endElement(HtmlConstants.INPUT);
096      }
097    
098      /**
099       * Create a secret attribute in the session.
100       * Should usually be called in a {@link javax.servlet.http.HttpSessionListener}.
101       */
102      public static void create(HttpSession session) {
103        session.setAttribute(Secret.KEY, new Secret());
104      }
105    }