Package org.jclouds.chef.filters

Source Code of org.jclouds.chef.filters.SignedHeaderAuthTest

/*
* 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.jclouds.chef.filters;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertEqualsNoOrder;
import static org.testng.Assert.assertTrue;

import java.io.IOException;
import java.security.PrivateKey;

import javax.inject.Provider;
import javax.ws.rs.HttpMethod;

import org.jclouds.ContextBuilder;
import org.jclouds.chef.ChefApiMetadata;
import org.jclouds.crypto.Crypto;
import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.internal.SignatureWire;
import org.jclouds.logging.config.NullLoggingModule;
import org.jclouds.rest.internal.BaseRestApiTest.MockModule;
import org.jclouds.util.Strings2;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.net.HttpHeaders;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;

@Test(groups = { "unit" })
public class SignedHeaderAuthTest {

   public static final String USER_ID = "spec-user";
   public static final String BODY = "Spec Body";
   // Base64.encode64(Digest::SHA1.digest("Spec Body")).chomp
   public static final String HASHED_BODY = "DFteJZPVv6WKdQmMqZUQUumUyRs=";
   public static final String TIMESTAMP_ISO8601 = "2009-01-01T12:00:00Z";

   public static final String PATH = "/organizations/clownco";
   // Base64.encode64(Digest::SHA1.digest("/organizations/clownco")).chomp

   public static final String HASHED_CANONICAL_PATH = "YtBWDn1blGGuFIuKksdwXzHU9oE=";
   public static final String REQUESTING_ACTOR_ID = "c0f8a68c52bffa1020222a56b23cccfa";

   // Content hash is ???TODO
   public static final String X_OPS_CONTENT_HASH = "DFteJZPVv6WKdQmMqZUQUumUyRs=";

   public static final String[] X_OPS_AUTHORIZATION_LINES = new String[] {
         "jVHrNniWzpbez/eGWjFnO6lINRIuKOg40ZTIQudcFe47Z9e/HvrszfVXlKG4",
         "NMzYZgyooSvU85qkIUmKuCqgG2AIlvYa2Q/2ctrMhoaHhLOCWWoqYNMaEqPc",
         "3tKHE+CfvP+WuPdWk4jv4wpIkAz6ZLxToxcGhXmZbXpk56YTmqgBW2cbbw4O",
         "IWPZDHSiPcw//AYNgW1CCDptt+UFuaFYbtqZegcBd2n/jzcWODA7zL4KWEUy",
         "9q4rlh/+1tBReg60QdsmDRsw/cdO1GZrKtuCwbuD4+nbRdVBKv72rqHX9cu0", "utju9jzczCyB+sSAQWrxSsXB/b8vV2qs0l4VD2ML+w==" };

   // We expect Mixlib::Authentication::SignedHeaderAuth//sign to return this
   // if passed the BODY above.
   public static final Multimap<String, String> EXPECTED_SIGN_RESULT = ImmutableMultimap.<String, String> builder()
         .put("X-Ops-Content-Hash", X_OPS_CONTENT_HASH).put("X-Ops-Userid", USER_ID).put("X-Ops-Sign", "version=1.0")
         .put("X-Ops-Authorization-1", X_OPS_AUTHORIZATION_LINES[0])
         .put("X-Ops-Authorization-2", X_OPS_AUTHORIZATION_LINES[1])
         .put("X-Ops-Authorization-3", X_OPS_AUTHORIZATION_LINES[2])
         .put("X-Ops-Authorization-4", X_OPS_AUTHORIZATION_LINES[3])
         .put("X-Ops-Authorization-5", X_OPS_AUTHORIZATION_LINES[4])
         .put("X-Ops-Authorization-6", X_OPS_AUTHORIZATION_LINES[5]).put("X-Ops-Timestamp", TIMESTAMP_ISO8601).build();

   // Content hash for empty string
   public static final String X_OPS_CONTENT_HASH_EMPTY = "2jmj7l5rSw0yVb/vlWAYkK/YBwk=";
   public static final Multimap<String, String> EXPECTED_SIGN_RESULT_EMPTY = ImmutableMultimap
         .<String, String> builder().put("X-Ops-Content-Hash", X_OPS_CONTENT_HASH_EMPTY).put("X-Ops-Userid", USER_ID)
         .put("X-Ops-Sign", "version=1.0")
         .put("X-Ops-Authorization-1", "N6U75kopDK64cEFqrB6vw+PnubnXr0w5LQeXnIGNGLRP2LvifwIeisk7QxEx")
         .put("X-Ops-Authorization-2", "mtpQOWAw8HvnWErjzuk9AvUsqVmWpv14ficvkaD79qsPMvbje+aLcIrCGT1P")
         .put("X-Ops-Authorization-3", "3d2uvf4w7iqwzrIscPnkxLR6o6pymR90gvJXDPzV7Le0jbfD8kmZ8AAK0sGG")
         .put("X-Ops-Authorization-4", "09F1ftW80bLatJTA66Cw2wBz261r6x/abZhIKFJFDWLzyQGJ8ZNOkUrDDtgI")
         .put("X-Ops-Authorization-5", "svLVXpOJKZZfKunsElpWjjsyNt3k8vpI1Y4ANO8Eg2bmeCPeEK+YriGm5fbC")
         .put("X-Ops-Authorization-6", "DzWNPylHJqMeGKVYwGQKpg62QDfe5yXh3wZLiQcXow==")
         .put("X-Ops-Timestamp", TIMESTAMP_ISO8601).build();

   public static String PUBLIC_KEY;
   public static String PRIVATE_KEY;

   static {
      try {
         PUBLIC_KEY = Strings2.toStringAndClose(SignedHeaderAuthTest.class.getResourceAsStream("/pubkey.txt"));

         PRIVATE_KEY = Strings2.toStringAndClose(SignedHeaderAuthTest.class.getResourceAsStream("/privkey.txt"));
      } catch (IOException e) {
         Throwables.propagate(e);
      }
   }

   @Test
   void canonicalizedPathRemovesMultipleSlashes() {
      assertEquals(signing_obj.canonicalPath("///"), "/");
   }

   @Test
   void canonicalizedPathRemovesTrailingSlash() {
      assertEquals(signing_obj.canonicalPath("/path/"), "/path");
   }

   @Test
   void shouldGenerateTheCorrectStringToSignAndSignature() {

      HttpRequest request = HttpRequest.builder().method(HttpMethod.POST).endpoint("http://localhost/" + PATH)
            .payload(BODY).build();

      String expected_string_to_sign = new StringBuilder().append("Method:POST").append("\n").append("Hashed Path:")
            .append(HASHED_CANONICAL_PATH).append("\n").append("X-Ops-Content-Hash:").append(HASHED_BODY).append("\n")
            .append("X-Ops-Timestamp:").append(TIMESTAMP_ISO8601).append("\n").append("X-Ops-UserId:").append(USER_ID)
            .toString();

      assertEquals(signing_obj.createStringToSign("POST", HASHED_CANONICAL_PATH, HASHED_BODY, TIMESTAMP_ISO8601),
            expected_string_to_sign);
      assertEquals(signing_obj.sign(expected_string_to_sign), Joiner.on("").join(X_OPS_AUTHORIZATION_LINES));

      request = signing_obj.filter(request);
      Multimap<String, String> headersWithoutContentLength = LinkedHashMultimap.create(request.getHeaders());
      headersWithoutContentLength.removeAll(HttpHeaders.CONTENT_LENGTH);
      assertEqualsNoOrder(headersWithoutContentLength.values().toArray(), EXPECTED_SIGN_RESULT.values().toArray());
   }

   @Test
   void shouldGenerateTheCorrectStringToSignAndSignatureWithNoBody() {

      HttpRequest request = HttpRequest.builder().method(HttpMethod.DELETE).endpoint("http://localhost/" + PATH)
            .build();

      request = signing_obj.filter(request);
      Multimap<String, String> headersWithoutContentLength = LinkedHashMultimap.create(request.getHeaders());
      assertEqualsNoOrder(headersWithoutContentLength.entries().toArray(), EXPECTED_SIGN_RESULT_EMPTY.entries()
            .toArray());
   }

   @Test
   void shouldNotChokeWhenSigningARequestForAResourceWithALongName() {
      StringBuilder path = new StringBuilder("nodes/");
      for (int i = 0; i < 100; i++)
         path.append('A');
      HttpRequest request = HttpRequest.builder().method(HttpMethod.PUT)
            .endpoint("http://localhost/" + path.toString()).payload(BODY).build();

      signing_obj.filter(request);
   }

   @Test
   void shouldReplacePercentage3FWithQuestionMarkAtUrl() {
      StringBuilder path = new StringBuilder("nodes/");
      path.append("test/cookbooks/myCookBook%3Fnum_versions=5");
      HttpRequest request = HttpRequest.builder().method(HttpMethod.GET)
            .endpoint("http://localhost/" + path.toString()).payload(BODY).build();
      request = signing_obj.filter(request);
      assertTrue(request.getRequestLine().contains("?num_versions=5"));
   }

   private SignedHeaderAuth signing_obj;

   /**
    * before class, as we need to ensure that the filter is threadsafe.
    *
    * @throws IOException
    *
    */
   @BeforeClass
   protected void createFilter() throws IOException {

      Injector injector = ContextBuilder.newBuilder(new ChefApiMetadata()).credentials(USER_ID, PRIVATE_KEY)
            .modules(ImmutableSet.<Module> of(new MockModule(), new NullLoggingModule())).buildInjector();

      HttpUtils utils = injector.getInstance(HttpUtils.class);
      Crypto crypto = injector.getInstance(Crypto.class);

      Supplier<PrivateKey> privateKey = injector.getInstance(Key.get(new TypeLiteral<Supplier<PrivateKey>>() {
      }));

      signing_obj = new SignedHeaderAuth(new SignatureWire(),
            Suppliers.ofInstance(new Credentials(USER_ID, PRIVATE_KEY)), privateKey, new Provider<String>() {

               @Override
               public String get() {
                  return TIMESTAMP_ISO8601;
               }

            }, utils, crypto);
   }

}
TOP

Related Classes of org.jclouds.chef.filters.SignedHeaderAuthTest

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.