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 "&" as a delimiter
++ String[] tokens = token.split("(&|\\?|&)");
++ 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;
+ }
+
+}