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.portlet.PortletUtils;
024    import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
025    import org.apache.myfaces.tobago.renderkit.html.HtmlConstants;
026    
027    import javax.faces.context.FacesContext;
028    import javax.servlet.http.HttpSession;
029    import java.io.IOException;
030    import java.io.Serializable;
031    import java.security.SecureRandom;
032    import java.util.Map;
033    
034    public class Secret implements Serializable {
035    
036      private static final long serialVersionUID = 1L;
037    
038      private static final String KEY = Secret.class.getName();
039    
040      private static final SecureRandom RANDOM = new SecureRandom();
041    
042      private static final int SECRET_LENGTH = 16;
043    
044      private static final boolean COMMONS_CODEC_AVAILABLE = commonsCodecAvailable();
045    
046      private static boolean commonsCodecAvailable() {
047        try {
048          Base64.encodeBase64URLSafeString(new byte[0]);
049          return true;
050        } catch (Error e) {
051          return false;
052        }
053      }
054    
055      private String secret;
056    
057      private Secret() {
058        byte[] bytes = new byte[SECRET_LENGTH];
059        RANDOM.nextBytes(bytes);
060        secret = COMMONS_CODEC_AVAILABLE ? encodeBase64(bytes) : encodeHex(bytes);
061      }
062    
063      private String encodeBase64(byte[] bytes) {
064        return Base64.encodeBase64URLSafeString(bytes);
065      }
066    
067      private String encodeHex(byte[] bytes) {
068        StringBuilder builder = new StringBuilder(SECRET_LENGTH * 2);
069        for (byte b : bytes) {
070          builder.append(String.format("%02x", b));
071        }
072        return builder.toString();
073      }
074    
075      /**
076       * Checks that the request contains a parameter {@link org.apache.myfaces.tobago.webapp.Secret#KEY}
077       * which is equals to a secret value in the session.
078       */
079      public static boolean check(final FacesContext facesContext) {
080        final Map requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
081        final String fromRequest = (String) requestParameterMap.get(Secret.KEY);
082        final Object session = facesContext.getExternalContext().getSession(true);
083        final Secret secret;
084        if (session instanceof HttpSession) {
085          secret = (Secret) ((HttpSession) session).getAttribute(Secret.KEY);
086        } else {
087          secret = (Secret) PortletUtils.getAttributeFromSessionForApplication(session, Secret.KEY);
088        }
089        return secret != null && secret.secret.equals(fromRequest);
090      }
091    
092      /**
093       * Encode a hidden field with the secret value from the session.
094       */
095      public static void encode(final FacesContext facesContext, final TobagoResponseWriter writer) throws IOException {
096        writer.startElement(HtmlConstants.INPUT, null);
097        writer.writeAttribute(HtmlAttributes.TYPE, "hidden", false);
098        writer.writeAttribute(HtmlAttributes.NAME, Secret.KEY, false);
099        writer.writeAttribute(HtmlAttributes.ID, Secret.KEY, false);
100        final Object session = facesContext.getExternalContext().getSession(true);
101        final Secret secret;
102        if (session instanceof HttpSession) {
103          secret = (Secret) ((HttpSession) session).getAttribute(Secret.KEY);
104        } else {
105          secret = (Secret) PortletUtils.getAttributeFromSessionForApplication(session, Secret.KEY);
106        }
107        writer.writeAttribute(HtmlAttributes.VALUE, secret.secret, false);
108        writer.endElement(HtmlConstants.INPUT);
109      }
110    
111      /**
112       * Create a secret attribute in the session.
113       * Should usually be called in a {@link javax.servlet.http.HttpSessionListener}.
114       */
115      public static void create(HttpSession session) {
116        session.setAttribute(Secret.KEY, new Secret());
117      }
118    }