You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2017/10/16 17:06:10 UTC

[16/23] knox git commit: Merge branch 'master' into KNOX-998-Package_Restructuring

http://git-wip-us.apache.org/repos/asf/knox/blob/8affbc02/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
----------------------------------------------------------------------
diff --cc gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index 224eb1c,0000000..b73b1b7
mode 100644,000000..100644
--- a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++ b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@@ -1,307 -1,0 +1,510 @@@
 +/**
 + * 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.knox.gateway.service.knoxtoken;
 +
 +import org.apache.knox.gateway.service.knoxtoken.TokenResource;
 +import org.apache.knox.gateway.services.GatewayServices;
 +import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
 +import org.apache.knox.gateway.services.security.token.TokenServiceException;
 +import org.apache.knox.gateway.services.security.token.impl.JWT;
 +import org.apache.knox.gateway.services.security.token.impl.JWTToken;
++import org.apache.knox.gateway.security.PrimaryPrincipal;
++
 +import org.easymock.EasyMock;
 +import org.junit.Assert;
 +import org.junit.BeforeClass;
 +import org.junit.Test;
 +
 +import com.nimbusds.jose.JWSSigner;
 +import com.nimbusds.jose.JWSVerifier;
 +import com.nimbusds.jose.crypto.RSASSASigner;
 +import com.nimbusds.jose.crypto.RSASSAVerifier;
 +
 +import java.util.Map;
 +
 +import javax.security.auth.Subject;
 +import javax.servlet.ServletContext;
 +import javax.servlet.http.HttpServletRequest;
 +import javax.servlet.http.HttpServletResponse;
 +import javax.ws.rs.core.Response;
 +
 +import static org.junit.Assert.*;
 +
 +import java.io.PrintWriter;
 +import java.io.StringWriter;
 +import java.security.KeyPair;
 +import java.security.KeyPairGenerator;
 +import java.security.NoSuchAlgorithmException;
 +import java.security.Principal;
++import java.security.cert.X509Certificate;
 +import java.security.interfaces.RSAPrivateKey;
 +import java.security.interfaces.RSAPublicKey;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collections;
 +import java.util.HashMap;
 +import java.util.List;
 +
 +/**
 + * Some tests for the token service
 + */
 +public class TokenServiceResourceTest {
 +
 +  protected static RSAPublicKey publicKey;
 +  protected static RSAPrivateKey privateKey;
 +
 +  @BeforeClass
 +  public static void setup() throws Exception, NoSuchAlgorithmException {
 +    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
 +    kpg.initialize(1024);
 +    KeyPair KPair = kpg.generateKeyPair();
 +
 +    publicKey = (RSAPublicKey) KPair.getPublic();
 +    privateKey = (RSAPrivateKey) KPair.getPrivate();
 +  }
 +
 +  @Test
 +  public void testTokenService() throws Exception {
 +    Assert.assertTrue(true);
 +  }
 +
 +  @Test
 +  public void testClientData() throws Exception {
 +    TokenResource tr = new TokenResource();
 +
 +    Map<String,Object> clientDataMap = new HashMap<>();
 +    tr.addClientDataToMap("cookie.name=hadoop-jwt,test=value".split(","), clientDataMap);
 +    Assert.assertTrue(clientDataMap.size() == 2);
 +
 +    clientDataMap = new HashMap<>();
 +    tr.addClientDataToMap("cookie.name=hadoop-jwt".split(","), clientDataMap);
 +    Assert.assertTrue(clientDataMap.size() == 1);
 +
 +    clientDataMap = new HashMap<>();
 +    tr.addClientDataToMap("".split(","), clientDataMap);
 +    Assert.assertTrue(clientDataMap.size() == 0);
 +  }
 +
 +  @Test
 +  public void testGetToken() throws Exception {
 +    TokenResource tr = new TokenResource();
 +
 +    ServletContext context = EasyMock.createNiceMock(ServletContext.class);
 +    //tr.context = context;
 +    // tr.init();
 +
 +    HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
 +    EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes();
 +    Principal principal = EasyMock.createNiceMock(Principal.class);
 +    EasyMock.expect(principal.getName()).andReturn("alice").anyTimes();
 +    EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes();
 +
 +    GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
 +    EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services);
 +
 +    JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey);
 +    EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority);
 +
 +    StringWriter writer = new StringWriter();
 +    PrintWriter printWriter = new PrintWriter(writer);
 +    HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
 +    EasyMock.expect(response.getWriter()).andReturn(printWriter);
 +
 +    EasyMock.replay(principal, services, context, request, response);
 +
 +    tr.request = request;
 +    tr.response = response;
 +
 +    // Issue a token
 +    Response retResponse = tr.doGet();
 +
 +    assertEquals(200, retResponse.getStatus());
 +
 +    // Parse the response
 +    String retString = writer.toString();
 +    String accessToken = getTagValue(retString, "access_token");
 +    assertNotNull(accessToken);
 +    String expiry = getTagValue(retString, "expires_in");
 +    assertNotNull(expiry);
 +
 +    // Verify the token
 +    JWTToken parsedToken = new JWTToken(accessToken);
 +    assertEquals("alice", parsedToken.getSubject());
 +    assertTrue(authority.verifyToken(parsedToken));
 +  }
 +
 +  @Test
 +  public void testAudiences() throws Exception {
 +
 +    ServletContext context = EasyMock.createNiceMock(ServletContext.class);
 +    EasyMock.expect(context.getInitParameter("knox.token.audiences")).andReturn("recipient1,recipient2");
 +    EasyMock.expect(context.getInitParameter("knox.token.ttl")).andReturn(null);
 +    EasyMock.expect(context.getInitParameter("knox.token.target.url")).andReturn(null);
 +    EasyMock.expect(context.getInitParameter("knox.token.client.data")).andReturn(null);
 +
 +    HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
 +    EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes();
 +    Principal principal = EasyMock.createNiceMock(Principal.class);
 +    EasyMock.expect(principal.getName()).andReturn("alice").anyTimes();
 +    EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes();
 +
 +    GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
 +    EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services);
 +
 +    JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey);
 +    EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority);
 +
 +    StringWriter writer = new StringWriter();
 +    PrintWriter printWriter = new PrintWriter(writer);
 +    HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
 +    EasyMock.expect(response.getWriter()).andReturn(printWriter);
 +
 +    EasyMock.replay(principal, services, context, request, response);
 +
 +    TokenResource tr = new TokenResource();
 +    tr.request = request;
 +    tr.response = response;
 +    tr.context = context;
 +    tr.init();
 +
 +    // Issue a token
 +    Response retResponse = tr.doGet();
 +
 +    assertEquals(200, retResponse.getStatus());
 +
 +    // Parse the response
 +    String retString = writer.toString();
 +    String accessToken = getTagValue(retString, "access_token");
 +    assertNotNull(accessToken);
 +    String expiry = getTagValue(retString, "expires_in");
 +    assertNotNull(expiry);
 +
 +    // Verify the token
 +    JWTToken parsedToken = new JWTToken(accessToken);
 +    assertEquals("alice", parsedToken.getSubject());
 +    assertTrue(authority.verifyToken(parsedToken));
 +
 +    // Verify the audiences
 +    List<String> audiences = Arrays.asList(parsedToken.getAudienceClaims());
 +    assertEquals(2, audiences.size());
 +    assertTrue(audiences.contains("recipient1"));
 +    assertTrue(audiences.contains("recipient2"));
 +  }
 +
++  @Test
++  public void testAudiencesWhitespace() throws Exception {
++
++    ServletContext context = EasyMock.createNiceMock(ServletContext.class);
++    EasyMock.expect(context.getInitParameter("knox.token.audiences")).andReturn(" recipient1, recipient2 ");
++    EasyMock.expect(context.getInitParameter("knox.token.ttl")).andReturn(null);
++    EasyMock.expect(context.getInitParameter("knox.token.target.url")).andReturn(null);
++    EasyMock.expect(context.getInitParameter("knox.token.client.data")).andReturn(null);
++
++    HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++    EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes();
++    Principal principal = EasyMock.createNiceMock(Principal.class);
++    EasyMock.expect(principal.getName()).andReturn("alice").anyTimes();
++    EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes();
++
++    GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
++    EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services);
++
++    JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey);
++    EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority);
++
++    StringWriter writer = new StringWriter();
++    PrintWriter printWriter = new PrintWriter(writer);
++    HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++    EasyMock.expect(response.getWriter()).andReturn(printWriter);
++
++    EasyMock.replay(principal, services, context, request, response);
++
++    TokenResource tr = new TokenResource();
++    tr.request = request;
++    tr.response = response;
++    tr.context = context;
++    tr.init();
++
++    // Issue a token
++    Response retResponse = tr.doGet();
++
++    assertEquals(200, retResponse.getStatus());
++
++    // Parse the response
++    String retString = writer.toString();
++    String accessToken = getTagValue(retString, "access_token");
++    assertNotNull(accessToken);
++    String expiry = getTagValue(retString, "expires_in");
++    assertNotNull(expiry);
++
++    // Verify the token
++    JWTToken parsedToken = new JWTToken(accessToken);
++    assertEquals("alice", parsedToken.getSubject());
++    assertTrue(authority.verifyToken(parsedToken));
++
++    // Verify the audiences
++    List<String> audiences = Arrays.asList(parsedToken.getAudienceClaims());
++    assertEquals(2, audiences.size());
++    assertTrue(audiences.contains("recipient1"));
++    assertTrue(audiences.contains("recipient2"));
++  }
++
++  @Test
++  public void testValidClientCert() throws Exception {
++
++    ServletContext context = EasyMock.createNiceMock(ServletContext.class);
++    EasyMock.expect(context.getInitParameter("knox.token.client.cert.required")).andReturn("true");
++    EasyMock.expect(context.getInitParameter("knox.token.allowed.principals")).andReturn("CN=localhost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US");
++
++    HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++    EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes();
++    X509Certificate trustedCertMock = EasyMock.createMock(X509Certificate.class);
++    EasyMock.expect(trustedCertMock.getSubjectDN()).andReturn(new PrimaryPrincipal("CN=localhost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US")).anyTimes();
++    ArrayList<X509Certificate> certArrayList = new ArrayList<X509Certificate>();
++    certArrayList.add(trustedCertMock);
++    X509Certificate[] certs = {};
++    EasyMock.expect(request.getAttribute("javax.servlet.request.X509Certificate")).andReturn(certArrayList.toArray(certs)).anyTimes();
++
++    Principal principal = EasyMock.createNiceMock(Principal.class);
++    EasyMock.expect(principal.getName()).andReturn("alice").anyTimes();
++    EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes();
++
++    GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
++    EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services);
++
++    JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey);
++    EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority);
++
++    StringWriter writer = new StringWriter();
++    PrintWriter printWriter = new PrintWriter(writer);
++    HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++    EasyMock.expect(response.getWriter()).andReturn(printWriter);
++
++    EasyMock.replay(principal, services, context, request, response, trustedCertMock);
++
++    TokenResource tr = new TokenResource();
++    tr.request = request;
++    tr.response = response;
++    tr.context = context;
++    tr.init();
++
++    // Issue a token
++    Response retResponse = tr.doGet();
++
++    assertEquals(200, retResponse.getStatus());
++
++    // Parse the response
++    String retString = writer.toString();
++    String accessToken = getTagValue(retString, "access_token");
++    assertNotNull(accessToken);
++    String expiry = getTagValue(retString, "expires_in");
++    assertNotNull(expiry);
++
++    // Verify the token
++    JWTToken parsedToken = new JWTToken(accessToken);
++    assertEquals("alice", parsedToken.getSubject());
++    assertTrue(authority.verifyToken(parsedToken));
++  }
++
++  @Test
++  public void testValidClientCertWrongUser() throws Exception {
++
++    ServletContext context = EasyMock.createNiceMock(ServletContext.class);
++    EasyMock.expect(context.getInitParameter("knox.token.client.cert.required")).andReturn("true");
++    EasyMock.expect(context.getInitParameter("knox.token.allowed.principals")).andReturn("CN=remotehost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US");
++
++    HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++    EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes();
++    X509Certificate trustedCertMock = EasyMock.createMock(X509Certificate.class);
++    EasyMock.expect(trustedCertMock.getSubjectDN()).andReturn(new PrimaryPrincipal("CN=localhost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US")).anyTimes();
++    ArrayList<X509Certificate> certArrayList = new ArrayList<X509Certificate>();
++    certArrayList.add(trustedCertMock);
++    X509Certificate[] certs = {};
++    EasyMock.expect(request.getAttribute("javax.servlet.request.X509Certificate")).andReturn(certArrayList.toArray(certs)).anyTimes();
++
++    Principal principal = EasyMock.createNiceMock(Principal.class);
++    EasyMock.expect(principal.getName()).andReturn("alice").anyTimes();
++    EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes();
++
++    GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
++    EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services);
++
++    JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey);
++    EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority);
++
++    StringWriter writer = new StringWriter();
++    PrintWriter printWriter = new PrintWriter(writer);
++    HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++    EasyMock.expect(response.getWriter()).andReturn(printWriter);
++
++    EasyMock.replay(principal, services, context, request, response, trustedCertMock);
++
++    TokenResource tr = new TokenResource();
++    tr.request = request;
++    tr.response = response;
++    tr.context = context;
++    tr.init();
++
++    // Issue a token
++    Response retResponse = tr.doGet();
++
++    assertEquals(403, retResponse.getStatus());
++  }
++
++  @Test
++  public void testMissingClientCert() throws Exception {
++
++    ServletContext context = EasyMock.createNiceMock(ServletContext.class);
++    EasyMock.expect(context.getInitParameter("knox.token.client.cert.required")).andReturn("true");
++    EasyMock.expect(context.getInitParameter("knox.token.allowed.principals")).andReturn("CN=remotehost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US");
++
++    HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class);
++    EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes();
++    EasyMock.expect(request.getAttribute("javax.servlet.request.X509Certificate")).andReturn(null).anyTimes();
++
++    Principal principal = EasyMock.createNiceMock(Principal.class);
++    EasyMock.expect(principal.getName()).andReturn("alice").anyTimes();
++    EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes();
++
++    GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
++    EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services);
++
++    JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey);
++    EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority);
++
++    StringWriter writer = new StringWriter();
++    PrintWriter printWriter = new PrintWriter(writer);
++    HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class);
++    EasyMock.expect(response.getWriter()).andReturn(printWriter);
++
++    EasyMock.replay(principal, services, context, request, response);
++
++    TokenResource tr = new TokenResource();
++    tr.request = request;
++    tr.response = response;
++    tr.context = context;
++    tr.init();
++
++    // Issue a token
++    Response retResponse = tr.doGet();
++
++    assertEquals(403, retResponse.getStatus());
++  }
++
 +  private String getTagValue(String token, String tagName) {
 +    String searchString = tagName + "\":";
 +    String value = token.substring(token.indexOf(searchString) + searchString.length());
 +    if (value.startsWith("\"")) {
 +      value = value.substring(1);
 +    }
 +    if (value.contains("\"")) {
 +      return value.substring(0, value.indexOf("\""));
 +    } else if (value.contains(",")) {
 +      return value.substring(0, value.indexOf(","));
 +    } else {
 +      return value.substring(0, value.length() - 1);
 +    }
 +  }
 +
 +  private static class TestJWTokenAuthority implements JWTokenAuthority {
 +
 +    private RSAPublicKey publicKey;
 +    private RSAPrivateKey privateKey;
 +
 +    public TestJWTokenAuthority(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
 +      this.publicKey = publicKey;
 +      this.privateKey = privateKey;
 +    }
 +
 +    @Override
 +    public JWT issueToken(Subject subject, String algorithm)
 +      throws TokenServiceException {
 +      Principal p = (Principal) subject.getPrincipals().toArray()[0];
 +      return issueToken(p, algorithm);
 +    }
 +
 +    @Override
 +    public JWT issueToken(Principal p, String algorithm)
 +      throws TokenServiceException {
 +      return issueToken(p, null, algorithm);
 +    }
 +
 +    @Override
 +    public JWT issueToken(Principal p, String audience, String algorithm)
 +      throws TokenServiceException {
 +      return issueToken(p, audience, algorithm, -1);
 +    }
 +
 +    @Override
 +    public boolean verifyToken(JWT token) throws TokenServiceException {
 +      JWSVerifier verifier = new RSASSAVerifier(publicKey);
 +      return token.verify(verifier);
 +    }
 +
 +    @Override
 +    public JWT issueToken(Principal p, String audience, String algorithm,
 +                               long expires) throws TokenServiceException {
 +      ArrayList<String> audiences = null;
 +      if (audience != null) {
 +        audiences = new ArrayList<String>();
 +        audiences.add(audience);
 +      }
 +      return issueToken(p, audiences, algorithm, expires);
 +    }
 +
 +    @Override
 +    public JWT issueToken(Principal p, List<String> audiences, String algorithm,
 +                               long expires) throws TokenServiceException {
 +      String[] claimArray = new String[4];
 +      claimArray[0] = "KNOXSSO";
 +      claimArray[1] = p.getName();
 +      claimArray[2] = null;
 +      if (expires == -1) {
 +        claimArray[3] = null;
 +      } else {
 +        claimArray[3] = String.valueOf(expires);
 +      }
 +
 +      JWTToken token = null;
 +      if ("RS256".equals(algorithm)) {
 +        token = new JWTToken("RS256", claimArray, audiences);
 +        JWSSigner signer = new RSASSASigner(privateKey);
 +        token.sign(signer);
 +      } else {
 +        throw new TokenServiceException("Cannot issue token - Unsupported algorithm");
 +      }
 +
 +      return token;
 +    }
 +
 +    @Override
 +    public JWT issueToken(Principal p, String algorithm, long expiry)
 +        throws TokenServiceException {
 +      return issueToken(p, Collections.<String>emptyList(), algorithm, expiry);
 +    }
 +
 +    @Override
 +    public boolean verifyToken(JWT token, RSAPublicKey publicKey) throws TokenServiceException {
 +      JWSVerifier verifier = new RSASSAVerifier(publicKey);
 +      return token.verify(verifier);
 +    }
 +
 +  }
 +
 +
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/8affbc02/gateway-shell-release/pom.xml
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/knox/blob/8affbc02/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Parser.java
----------------------------------------------------------------------
diff --cc gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Parser.java
index 47ed00c,0000000..1d58978
mode 100644,000000..100644
--- a/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Parser.java
+++ b/gateway-util-urltemplate/src/main/java/org/apache/knox/gateway/util/urltemplate/Parser.java
@@@ -1,345 -1,0 +1,349 @@@
 +/**
 + * 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.knox.gateway.util.urltemplate;
 +
 +import org.apache.knox.gateway.i18n.resources.ResourcesFactory;
 +
 +import java.net.URISyntaxException;
 +import java.util.StringTokenizer;
 +import java.util.regex.Matcher;
 +import java.util.regex.Pattern;
 +
 +//NOTE: Instances Not thread safe but reusable.  Static parse method is thread safe.
 +//NOTE: Ignores matrix parameters at this point.
 +public class Parser {
 +
 +  /*
 +      ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
 +       12            3  4          5       6  7        8 9
 +
 +   The numbers in the second line above are only to assist readability;
 +   they indicate the reference points for each subexpression (i.e., each
 +   paired parenthesis).  We refer to the value matched for subexpression
 +   <n> as $<n>.  For example, matching the above expression to
 +
 +      http://www.ics.uci.edu/pub/ietf/uri/#Related
 +
 +   results in the following subexpression matches:
 +
 +      $1 = http:
 +      $2 = http
 +      $3 = //www.ics.uci.edu
 +      $4 = www.ics.uci.edu
 +      $5 = /pub/ietf/uri/
 +      $6 = <undefined>
 +      $7 = <undefined>
 +      $8 = #Related
 +      $9 = Related
 +
 +   where <undefined> indicates that the component is not present, as is
 +   the case for the query component in the above example.  Therefore, we
 +   can determine the value of the five components as
 +
 +      scheme    = $2
 +      authority = $4
 +      path      = $5
 +      query     = $7
 +      fragment  = $9
 +   */
 +
 +  private static final Resources RES = ResourcesFactory.get( Resources.class );
 +
 +  public static final char TEMPLATE_OPEN_MARKUP = '{';
 +  public static final char TEMPLATE_CLOSE_MARKUP = '}';
 +  public static final char NAME_PATTERN_SEPARATOR = '=';
 +
 +  private static final int MATCH_GROUP_SCHEME = 1;
 +  private static final int MATCH_GROUP_SCHEME_NAKED = 2;
 +  private static final int MATCH_GROUP_AUTHORITY = 3;
 +  private static final int MATCH_GROUP_AUTHORITY_NAKED = 4;
 +  private static final int MATCH_GROUP_PATH = 5;
 +  private static final int MATCH_GROUP_QUERY = 6;
 +  private static final int MATCH_GROUP_QUERY_NAKED = 7;
 +  private static final int MATCH_GROUP_FRAGMENT = 8;
 +  private static final int MATCH_GROUP_FRAGMENT_NAKED = 9;
 +
 +  private static Pattern PATTERN = Pattern.compile( "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?" );
 +
 +  @Deprecated
 +  public static final Template parse( String template ) throws URISyntaxException {
 +    return Parser.parseTemplate( template );
 +  }
 +
 +  public static final Template parseTemplate( final String template ) throws URISyntaxException {
 +    Builder builder = new Builder( template );
 +    return parseInternal( builder );
 +  }
 +
 +  public static final Template parseLiteral( final String literal ) throws URISyntaxException {
 +    Builder builder = new Builder( literal );
 +    builder.setLiteral( true );
 +    return parseInternal( builder );
 +  }
 +
 +  private static final Template parseInternal( final Builder builder ) throws URISyntaxException {
 +    String original = builder.getOriginal();
 +    builder.setHasScheme( false );
 +    builder.setHasAuthority( false ); // Assume no until found otherwise.  If true, will cause // in output URL.
 +    builder.setIsAuthorityOnly( false );
 +    builder.setIsAbsolute( false ); // Assume relative until found otherwise.  If true, will cause leading / in output URL.
 +    builder.setIsDirectory( false ); // Assume a file path until found otherwise.  If true, will cause trailing / in output URL.
 +    builder.setHasQuery( false ); // Assume no ? until found otherwise.  If true, will cause ? in output URL.
 +    builder.setHasFragment( false ); // Assume no # until found otherwise.  If true, will cause # in output URL.
 +    Matcher match = PATTERN.matcher( original );
 +    if( match.matches() ) {
 +      consumeSchemeMatch( builder, match );
 +      consumeAuthorityMatch( builder, match );
 +      consumePathMatch( builder, match );
 +      consumeQueryMatch( builder, match );
 +      consumeFragmentMatch( builder, match );
 +      fixNakedAuthority( builder );
 +    } else {
 +      throw new URISyntaxException( original, RES.parseTemplateFailureReason( original ) );
 +    }
 +    return builder.build();
 +  }
 +
 +  private static final void fixNakedAuthority( final Builder builder ) {
 +    if( builder.getHasScheme() &&
 +        !builder.getHasAuthority() &&
 +        !builder.getIsAbsolute() &&
 +        !builder.getIsDirectory() &&
 +        ( builder.getPath().size() == 1 ) &&
 +        !builder.getHasQuery() &&
 +        !builder.getHasFragment() ) {
 +      final Scheme scheme = builder.getScheme();
 +      builder.setHasScheme( false );
 +      builder.setHost( makeTokenSingular( scheme.getToken() ) );
 +      Path path = builder.getPath().remove( 0 );
 +      builder.setPort( makeTokenSingular( path.getToken() ) );
 +      builder.setIsAuthorityOnly( true );
 +    }
 +  }
 +
 +  private static final Token makeTokenSingular( Token token ) {
 +    final String effectivePattern = token.getEffectivePattern();
 +    if( Segment.GLOB_PATTERN.equals( effectivePattern ) ) {
 +      token = new Token( token.getParameterName(), token.getOriginalPattern(), Segment.STAR_PATTERN, token.isLiteral() );
 +    }
 +    return token;
 +  }
 +
 +//  private String makePatternSingular( String pattern ) {
 +//    if( Segment.GLOB_PATTERN.equals( pattern ) ) {
 +//      pattern = Segment.STAR_PATTERN;
 +//    }
 +//    return pattern;
 +//  }
 +
 +  private static void consumeSchemeMatch( final Builder builder, final Matcher match ) {
 +    if( match.group( MATCH_GROUP_SCHEME ) != null ) {
 +      builder.setHasScheme( true );
 +      consumeSchemeToken( builder, match.group( MATCH_GROUP_SCHEME_NAKED ) );
 +    }
 +  }
 +
 +  private static void consumeSchemeToken( final Builder builder, final String token ) {
 +    if( token != null ) {
 +      Token t = parseTemplateToken( builder, token, Segment.STAR_PATTERN );
 +      builder.setScheme( t );
 +    }
 +  }
 +
 +  private static void consumeAuthorityMatch( final Builder builder, final Matcher match ) {
 +    if( match.group( MATCH_GROUP_AUTHORITY ) != null ) {
 +      builder.setHasAuthority( true );
 +      consumeAuthorityToken( builder, match.group( MATCH_GROUP_AUTHORITY_NAKED ) );
 +    }
 +  }
 +
 +  private static void consumeAuthorityToken( final Builder builder, final String token ) {
 +    if( token != null ) {
 +      Token paramPattern;
 +      String[] usernamePassword=null, hostPort;
 +      String[] userAddr = split( token, '@' );
 +      if( userAddr.length == 1 ) {
 +        hostPort = split( userAddr[ 0 ], ':' );
 +      } else {
 +        usernamePassword = split( userAddr[ 0 ], ':' );
 +        hostPort = split( userAddr[ 1 ], ':' );
 +      }
 +      if( usernamePassword != null ) {
 +        if( usernamePassword[ 0 ].length() > 0 ) {
 +          paramPattern = makeTokenSingular( parseTemplateToken( builder, usernamePassword[ 0 ], Segment.STAR_PATTERN ) );
 +          builder.setUsername( paramPattern );
 +        }
 +        if( usernamePassword.length > 1 && usernamePassword[ 1 ].length() > 0 ) {
 +          paramPattern = makeTokenSingular( parseTemplateToken( builder, usernamePassword[ 1 ], Segment.STAR_PATTERN ) );
 +          builder.setPassword( paramPattern );
 +        }
 +      }
 +      if( hostPort[ 0 ].length() > 0 ) {
 +        paramPattern = makeTokenSingular( parseTemplateToken( builder, hostPort[ 0 ], Segment.STAR_PATTERN ) );
 +        builder.setHost( paramPattern );
 +      }
 +      if( hostPort.length > 1 && hostPort[ 1 ].length() > 0 ) {
 +        paramPattern = makeTokenSingular( parseTemplateToken( builder, hostPort[ 1 ], Segment.STAR_PATTERN ) );
 +        builder.setPort( paramPattern );
 +      }
 +    }
 +  }
 +
 +  private static void consumePathMatch( final Builder builder, final Matcher match ) {
 +    String path = match.group( MATCH_GROUP_PATH );
 +    if( path != null ) {
 +      builder.setIsAbsolute( path.startsWith( "/" ) );
 +      builder.setIsDirectory( path.endsWith( "/" ) );
 +      consumePathToken( builder, path );
 +    }
 +  }
 +
 +  private static final void consumePathToken( final Builder builder, final String token ) {
 +    if( token != null ) {
 +      final StringTokenizer tokenizer = new StringTokenizer( token, "/" );
 +      while( tokenizer.hasMoreTokens() ) {
 +        consumePathSegment( builder, tokenizer.nextToken() );
 +      }
 +    }
 +  }
 +
 +  private static final void consumePathSegment( final Builder builder, final String token ) {
 +    if( token != null ) {
 +      final Token t = parseTemplateToken( builder, token, Segment.GLOB_PATTERN );
 +      builder.addPath( t );
 +    }
 +  }
 +
 +  private static void consumeQueryMatch( final Builder builder, Matcher match ) {
 +    if( match.group( MATCH_GROUP_QUERY ) != null ) {
 +      builder.setHasQuery( true );
 +      consumeQueryToken( builder, match.group( MATCH_GROUP_QUERY_NAKED ) );
 +    }
 +  }
 +
 +  private static void consumeQueryToken( final Builder builder, String token ) {
 +    if( token != null ) {
-       StringTokenizer tokenizer = new StringTokenizer( token, "?&" );
-       while( tokenizer.hasMoreTokens() ) {
-         consumeQuerySegment( builder, tokenizer.nextToken() );
++      //add "&amp;" as a delimiter
++      String[] tokens = token.split("(&amp;|\\?|&)");
++      if (tokens != null){
++        for (String nextToken : tokens){
++          consumeQuerySegment(builder,nextToken);
++        }
 +      }
++
 +    }
 +  }
 +
 +  private static void consumeQuerySegment( final Builder builder, String token ) {
 +    if( token != null && token.length() > 0 ) {
 +      // Shorthand format {queryParam} == queryParam={queryParam=*}
 +      if( TEMPLATE_OPEN_MARKUP == token.charAt( 0 ) ) {
 +        Token paramPattern = parseTemplateToken( builder, token, Segment.GLOB_PATTERN );
 +        String paramName = paramPattern.parameterName;
 +        if( paramPattern.originalPattern == null ) {
 +          builder.addQuery( paramName, new Token( paramName, null, Segment.GLOB_PATTERN, builder.isLiteral() ) );
 +//          if( Segment.STAR_PATTERN.equals( paramName ) || Segment.GLOB_PATTERN.equals( paramName ) ) {
 +//            builder.addQuery( paramName, new Token( paramName, null, Segment.GLOB_PATTERN ) );
 +//          } else {
 +//            builder.addQuery( paramName, new Token( paramName, null, Segment.GLOB_PATTERN ) );
 +//          }
 +        } else {
 +          builder.addQuery( paramName, new Token( paramName, paramPattern.originalPattern, builder.isLiteral() ) );
 +        }
 +      } else {
 +        String nameValue[] = split( token, '=' );
 +        if( nameValue.length == 1 ) {
 +          String queryName = nameValue[ 0 ];
 +          builder.addQuery( queryName, new Token( Segment.ANONYMOUS_PARAM, null, builder.isLiteral() ) );
 +        } else {
 +          String queryName = nameValue[ 0 ];
 +          Token paramPattern = parseTemplateToken( builder, nameValue[ 1 ], Segment.GLOB_PATTERN );
 +          builder.addQuery( queryName, paramPattern );
 +        }
 +      }
 +    }
 +  }
 +
 +  private static void consumeFragmentMatch( final Builder builder, Matcher match ) {
 +    if( match.group( MATCH_GROUP_FRAGMENT ) != null ) {
 +      builder.setHasFragment( true );
 +      consumeFragmentToken( builder, match.group( MATCH_GROUP_FRAGMENT_NAKED ) );
 +    }
 +  }
 +
 +  private static void consumeFragmentToken( final Builder builder, String token ) {
 +    if( token != null && token.length() > 0 ) {
 +      Token t = parseTemplateToken( builder, token, Segment.STAR_PATTERN );
 +      builder.setFragment( t );
 +    }
 +  }
 +
 +  static final Token parseTemplateToken( final Builder builder, final String s, final String defaultEffectivePattern ) {
 +    String paramName, actualPattern, effectivePattern;
 +    final int l = s.length();
 +    // If the token isn't the empty string, then
 +    if( l > 0 && !builder.isLiteral() ) {
 +      final int b = ( s.charAt( 0 ) == TEMPLATE_OPEN_MARKUP ? 1 : -1 );
 +      final int e = ( s.charAt( l-1 ) == TEMPLATE_CLOSE_MARKUP ? l-1 : -1 );
 +      // If this is a parameter template, ie {...}
 +      if( ( b > 0 ) && ( e > 0 ) && ( e > b ) ) {
 +        final int i = s.indexOf( NAME_PATTERN_SEPARATOR, b );
 +        // If this is an anonymous template
 +        if( i < 0 ) {
 +          paramName = s.substring( b, e );
 +          actualPattern = null;
 +          if( Segment.GLOB_PATTERN.equals( paramName ) ) {
 +            effectivePattern = Segment.GLOB_PATTERN;
 +          } else {
 +            effectivePattern = defaultEffectivePattern;
 +          }
 +        // Otherwise populate the NVP.
 +        } else {
 +          paramName = s.substring( b, i );
 +          actualPattern = s.substring( i+1, e );
 +          effectivePattern = actualPattern;
 +        }
 +      // Otherwise it is just a pattern.
 +      } else {
 +        paramName = Segment.ANONYMOUS_PARAM;
 +        actualPattern = s;
 +        effectivePattern = actualPattern;
 +      }
 +    // Otherwise the token has no value.
 +    } else {
 +      paramName = Segment.ANONYMOUS_PARAM;
 +      actualPattern = s;
 +      effectivePattern = actualPattern;
 +    }
 +    final Token token = new Token( paramName, actualPattern, effectivePattern, builder.isLiteral() );
 +    return token;
 +  }
 +
 +  // Using this because String.split is very inefficient.
 +  private static String[] split( String s, char d ) {
 +    String[] a;
 +    int i = s.indexOf( d );
 +    if( i < 0 ) {
 +      a = new String[]{ s };
 +    } else {
 +      a = new String[]{ s.substring( 0, i ), s.substring( i + 1 ) };
 +    }
 +    return a;
 +  }
 +
 +}