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