Package org.apache.wicket.core.request.mapper

Source Code of org.apache.wicket.core.request.mapper.CryptoMapper$ApplicationCryptProvider

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.core.request.mapper;

import java.util.List;

import org.apache.wicket.Application;
import org.apache.wicket.core.request.handler.RequestSettingRequestHandler;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Url;
import org.apache.wicket.util.IProvider;
import org.apache.wicket.util.crypt.ICrypt;
import org.apache.wicket.util.crypt.ICryptFactory;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Request mapper that encrypts urls generated by another mapper. The original URL (both segments
* and parameters) is encrypted and is represented as URL segment. To be able to handle relative
* URLs for images in .css file the same amount of URL segments that the original URL had are
* appended to the encrypted URL. Each segment has a precise 5 character value, calculated using a
* checksum. This helps in calculating the relative distance from the original URL. When a URL is
* returned by the browser, we iterate through these checksummed placeholder URL segments. If the
* segment matches the expected checksum, then the segment it deemed to be the corresponding segment
* in the encrypted URL. If the segment does not match the expected checksum, then the segment is
* deemed a plain text sibling of the corresponding segment in the encrypted URL, and all subsequent
* segments are considered plain text children of the current segment.
*
* @author igor.vaynberg
* @author Jesse Long
* @author svenmeier
*/
public class CryptoMapper implements IRequestMapper
{
  private static final Logger log = LoggerFactory.getLogger(CryptoMapper.class);

  private final IRequestMapper wrappedMapper;
  private final IProvider<ICrypt> cryptProvider;

  /**
   * Encrypt with {@link org.apache.wicket.settings.SecuritySettings#getCryptFactory()}.
   * <p>
   * Note: Encryption is done with {@link org.apache.wicket.settings.SecuritySettings#DEFAULT_ENCRYPTION_KEY}
   * if you haven't configured an alternative {@link ICryptFactory}. Alternatively use
   * {@link CryptoMapper#CryptoMapper(IRequestMapper, IProvider)} with a specific {@link ICrypt}.
   *
   * @param wrappedMapper
   *            the non-crypted request mapper
   * @param application
   *            the current application
   */
  public CryptoMapper(final IRequestMapper wrappedMapper, final Application application)
  {
    this(wrappedMapper, new ApplicationCryptProvider(application));
  }

  /**
   * Construct.
   *
   * @param wrappedMapper
   *            the non-crypted request mapper
   * @param cryptProvider
   *            the custom crypt provider
   */
  public CryptoMapper(final IRequestMapper wrappedMapper, final IProvider<ICrypt> cryptProvider)
  {
    this.wrappedMapper = Args.notNull(wrappedMapper, "wrappedMapper");
    this.cryptProvider = Args.notNull(cryptProvider, "cryptProvider");
  }

  @Override
  public int getCompatibilityScore(final Request request)
  {
    return wrappedMapper.getCompatibilityScore(request);
  }

  @Override
  public Url mapHandler(final IRequestHandler requestHandler)
  {
    final Url url = wrappedMapper.mapHandler(requestHandler);

    if (url == null)
    {
      return null;
    }

    if (url.isFull())
    {
      // do not encrypt full urls
      return url;
    }

    return encryptUrl(url);
  }

  @Override
  public IRequestHandler mapRequest(final Request request)
  {
    Url url = decryptUrl(request, request.getUrl());

    if (url == null)
    {
      return wrappedMapper.mapRequest(request);
    }

    Request decryptedRequest = request.cloneWithUrl(url);

    IRequestHandler handler = wrappedMapper.mapRequest(decryptedRequest);

    if (handler != null)
    {
      handler = new RequestSettingRequestHandler(decryptedRequest, handler);
    }

    return handler;
  }

  /**
   * @return the {@link ICrypt} implementation that may be used to encrypt/decrypt {@link Url}'s
   *         segments and/or query string
   */
  protected final ICrypt getCrypt()
  {
    return cryptProvider.get();
  }

  /**
   * @return the wrapped root request mapper
   */
  protected final IRequestMapper getWrappedMapper()
  {
    return wrappedMapper;
  }

  protected Url encryptUrl(final Url url)
  {
    if (url.getSegments().isEmpty())
    {
      return url;
    }
    String encryptedUrlString = getCrypt().encryptUrlSafe(url.toString());

    Url encryptedUrl = new Url(url.getCharset());
    encryptedUrl.getSegments().add(encryptedUrlString);

    int numberOfSegments = url.getSegments().size();
    HashedSegmentGenerator generator = new HashedSegmentGenerator(encryptedUrlString);
    for (int segNo = 0; segNo < numberOfSegments; segNo++)
    {
      encryptedUrl.getSegments().add(generator.next());
    }
    return encryptedUrl;
  }

  protected Url decryptUrl(final Request request, final Url encryptedUrl)
  {
    /*
     * If the encrypted URL has no segments it is the home page URL, and does not need
     * decrypting.
     */
    if (encryptedUrl.getSegments().isEmpty())
    {
      return encryptedUrl;
    }

    List<String> encryptedSegments = encryptedUrl.getSegments();

    Url url = new Url(request.getCharset());
    try
    {
      /*
       * The first encrypted segment contains an encrypted version of the entire plain text
       * url.
       */
      String encryptedUrlString = encryptedSegments.get(0);
      if (Strings.isEmpty(encryptedUrlString))
      {
        return null;
      }

      String decryptedUrl = getCrypt().decryptUrlSafe(encryptedUrlString);
      if (decryptedUrl == null)
      {
        return null;
      }
      Url originalUrl = Url.parse(decryptedUrl, request.getCharset());

      int originalNumberOfSegments = originalUrl.getSegments().size();
      int encryptedNumberOfSegments = encryptedUrl.getSegments().size();

      HashedSegmentGenerator generator = new HashedSegmentGenerator(encryptedUrlString);
      int segNo = 1;
      for (; segNo < encryptedNumberOfSegments; segNo++)
      {
        if (segNo > originalNumberOfSegments)
        {
          break;
        }

        String next = generator.next();
        String encryptedSegment = encryptedSegments.get(segNo);
        if (!next.equals(encryptedSegment))
        {
          /*
           * This segment received from the browser is not the same as the expected
           * segment generated by the HashSegmentGenerator. Hence it, and all subsequent
           * segments are considered plain text siblings of the original encrypted url.
           */
          break;
        }

        /*
         * This segments matches the expected checksum, so we add the corresponding segment
         * from the original URL.
         */
        url.getSegments().add(originalUrl.getSegments().get(segNo - 1));
      }
      /*
       * Add all remaining segments from the encrypted url as plain text segments.
       */
      for (; segNo < encryptedNumberOfSegments; segNo++)
      {
        // modified or additional segment
        url.getSegments().add(encryptedUrl.getSegments().get(segNo));
      }

      url.getQueryParameters().addAll(originalUrl.getQueryParameters());
      // WICKET-4923 additional parameters
      url.getQueryParameters().addAll(encryptedUrl.getQueryParameters());
    }
    catch (Exception e)
    {
      log.error("Error decrypting URL", e);
      url = null;
    }

    return url;
  }

  private static class ApplicationCryptProvider implements IProvider<ICrypt>
  {
    private final Application application;

    public ApplicationCryptProvider(final Application application)
    {
      this.application = application;
    }

    @Override
    public ICrypt get()
    {
      return application.getSecuritySettings().getCryptFactory().newCrypt();
    }
  }

  /**
   * A generator of hashed segments.
   */
  public static class HashedSegmentGenerator
  {
    private char[] characters;

    private int hash = 0;

    public HashedSegmentGenerator(String string)
    {
      characters = string.toCharArray();
    }

    /**
     * Generate the next segment
     *
     * @return segment
     */
    public String next()
    {
      char a = characters[Math.abs(hash % characters.length)];
      hash++;
      char b = characters[Math.abs(hash % characters.length)];
      hash++;
      char c = characters[Math.abs(hash % characters.length)];

      String segment = "" + a + b + c;
      hash = hashString(segment);

      segment += String.format("%02x", Math.abs(hash % 256));
      hash = hashString(segment);

      return segment;
    }

    public int hashString(final String str)
    {
      int hash = 97;

      for (char c : str.toCharArray())
      {
        int i = c;
        hash = 47 * hash + i;
      }

      return hash;
    }
  }
}
TOP

Related Classes of org.apache.wicket.core.request.mapper.CryptoMapper$ApplicationCryptProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.