Repository: nifi
Updated Branches:
  refs/heads/master 4bf267c8b -> bd88e4335


http://git-wip-us.apache.org/repos/asf/nifi/blob/bd88e433/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java
new file mode 100644
index 0000000..43aea86
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.nifi.web.security.x509;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authentication.AuthenticationResponse;
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.InvalidAuthenticationException;
+import org.apache.nifi.web.security.UntrustedProxyException;
+import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
+import org.junit.Before;
+import org.junit.Test;
+
+public class X509AuthenticationProviderTest {
+
+    private static final String INVALID_CERTIFICATE = "invalid-certificate";
+    private static final String IDENTITY_1 = "identity-1";
+    private static final String ANONYMOUS = "";
+
+    private static final String UNTRUSTED_PROXY = "untrusted-proxy";
+    private static final String PROXY_1 = "proxy-1";
+    private static final String PROXY_2 = "proxy-2";
+
+    private static final String GT = ">";
+    private static final String ESCAPED_GT = "\\\\>";
+    private static final String LT = "<";
+    private static final String ESCAPED_LT = "\\\\<";
+
+    private X509AuthenticationProvider x509AuthenticationProvider;
+    private X509IdentityProvider certificateIdentityProvider;
+    private SubjectDnX509PrincipalExtractor extractor;
+    private Authorizer authorizer;
+
+    @Before
+    public void setup() {
+        extractor = new SubjectDnX509PrincipalExtractor();
+
+        certificateIdentityProvider = mock(X509IdentityProvider.class);
+        
when(certificateIdentityProvider.authenticate(any(X509Certificate[].class))).then(invocation
 -> {
+            final X509Certificate[] certChain = invocation.getArgumentAt(0, 
X509Certificate[].class);
+            final String identity = 
extractor.extractPrincipal(certChain[0]).toString();
+
+            if (INVALID_CERTIFICATE.equals(identity)) {
+                throw new IllegalArgumentException();
+            }
+
+            return new AuthenticationResponse(identity, identity, 
TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS), "");
+        });
+
+        authorizer = mock(Authorizer.class);
+        
when(authorizer.authorize(any(AuthorizationRequest.class))).then(invocation -> {
+            final AuthorizationRequest request = invocation.getArgumentAt(0, 
AuthorizationRequest.class);
+
+            if (UNTRUSTED_PROXY.equals(request.getIdentity())) {
+                return AuthorizationResult.denied();
+            }
+
+            return AuthorizationResult.approved();
+        });
+
+        x509AuthenticationProvider = new 
X509AuthenticationProvider(certificateIdentityProvider, authorizer, 
NiFiProperties.createBasicNiFiProperties(null, null));
+    }
+
+    @Test(expected = InvalidAuthenticationException.class)
+    public void testInvalidCertificate() {
+        x509AuthenticationProvider.authenticate(getX509Request("", 
INVALID_CERTIFICATE));
+    }
+
+    @Test
+    public void testNoProxyChain() {
+        final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) 
x509AuthenticationProvider.authenticate(getX509Request("", IDENTITY_1));
+        final NiFiUser user = ((NiFiUserDetails) 
auth.getDetails()).getNiFiUser();
+
+        assertNotNull(user);
+        assertEquals(IDENTITY_1, user.getIdentity());
+        assertFalse(user.isAnonymous());
+    }
+
+    @Test(expected = UntrustedProxyException.class)
+    public void testUntrustedProxy() {
+        
x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(IDENTITY_1),
 UNTRUSTED_PROXY));
+    }
+
+    @Test
+    public void testOneProxy() {
+        final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) 
x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(IDENTITY_1),
 PROXY_1));
+        final NiFiUser user = ((NiFiUserDetails) 
auth.getDetails()).getNiFiUser();
+
+        assertNotNull(user);
+        assertEquals(IDENTITY_1, user.getIdentity());
+        assertFalse(user.isAnonymous());
+
+        assertNotNull(user.getChain());
+        assertEquals(PROXY_1, user.getChain().getIdentity());
+        assertFalse(user.getChain().isAnonymous());
+    }
+
+    @Test
+    public void testAnonymousWithOneProxy() {
+        final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) 
x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(ANONYMOUS),
 PROXY_1));
+        final NiFiUser user = ((NiFiUserDetails) 
auth.getDetails()).getNiFiUser();
+
+        assertNotNull(user);
+        assertEquals(StandardNiFiUser.ANONYMOUS_IDENTITY, user.getIdentity());
+        assertTrue(user.isAnonymous());
+
+        assertNotNull(user.getChain());
+        assertEquals(PROXY_1, user.getChain().getIdentity());
+        assertFalse(user.getChain().isAnonymous());
+    }
+
+    @Test
+    public void testTwoProxies() {
+        final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) 
x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(IDENTITY_1,
 PROXY_2), PROXY_1));
+        final NiFiUser user = ((NiFiUserDetails) 
auth.getDetails()).getNiFiUser();
+
+        assertNotNull(user);
+        assertEquals(IDENTITY_1, user.getIdentity());
+        assertFalse(user.isAnonymous());
+
+        assertNotNull(user.getChain());
+        assertEquals(PROXY_2, user.getChain().getIdentity());
+        assertFalse(user.getChain().isAnonymous());
+
+        assertNotNull(user.getChain().getChain());
+        assertEquals(PROXY_1, user.getChain().getChain().getIdentity());
+        assertFalse(user.getChain().getChain().isAnonymous());
+    }
+
+    @Test(expected = UntrustedProxyException.class)
+    public void testUntrustedProxyInChain() {
+        
x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(IDENTITY_1,
 UNTRUSTED_PROXY), PROXY_1));
+    }
+
+    @Test
+    public void testAnonymousProxyInChain() {
+        final NiFiAuthenticationToken auth = (NiFiAuthenticationToken) 
x509AuthenticationProvider.authenticate(getX509Request(buildProxyChain(IDENTITY_1,
 ANONYMOUS), PROXY_1));
+        final NiFiUser user = ((NiFiUserDetails) 
auth.getDetails()).getNiFiUser();
+
+        assertNotNull(user);
+        assertEquals(IDENTITY_1, user.getIdentity());
+        assertFalse(user.isAnonymous());
+
+        assertNotNull(user.getChain());
+        assertEquals(StandardNiFiUser.ANONYMOUS_IDENTITY, 
user.getChain().getIdentity());
+        assertTrue(user.getChain().isAnonymous());
+
+        assertNotNull(user.getChain().getChain());
+        assertEquals(PROXY_1, user.getChain().getChain().getIdentity());
+        assertFalse(user.getChain().getChain().isAnonymous());
+    }
+
+    @Test
+    public void testShouldCreateAnonymousUser() {
+        // Arrange
+        String identity = "someone";
+
+        // Act
+        NiFiUser user = X509AuthenticationProvider.createUser(identity, null, 
null, true);
+
+        // Assert
+        assert user != null;
+        assert user instanceof StandardNiFiUser;
+        assert user.getIdentity().equals(StandardNiFiUser.ANONYMOUS_IDENTITY);
+        assert user.isAnonymous();
+    }
+
+    @Test
+    public void testShouldCreateKnownUser() {
+        // Arrange
+        String identity = "someone";
+
+        // Act
+        NiFiUser user = X509AuthenticationProvider.createUser(identity, null, 
null, false);
+
+        // Assert
+        assert user != null;
+        assert user instanceof StandardNiFiUser;
+        assert user.getIdentity().equals(identity);
+        assert !user.isAnonymous();
+    }
+
+    private String buildProxyChain(final String... identities) {
+        List<String> elements = Arrays.asList(identities);
+        return 
StringUtils.join(elements.stream().map(X509AuthenticationProviderTest::formatDn).collect(Collectors.toList()),
 "");
+    }
+
+    private static String formatDn(String rawDn) {
+        return "<" + sanitizeDn(rawDn) + ">";
+    }
+
+    /**
+     * If a user provides a DN with the sequence '><', they could escape the 
tokenization process and impersonate another user.
+     * <p>
+     * Example:
+     * <p>
+     * Provided DN: {@code jdoe><alopresto} -> {@code 
<jdoe><alopresto><proxy...>} would allow the user to impersonate jdoe
+     *
+     * @param rawDn the unsanitized DN
+     * @return the sanitized DN
+     */
+    private static String sanitizeDn(String rawDn) {
+        if (StringUtils.isEmpty(rawDn)) {
+            return rawDn;
+        } else {
+            return rawDn.replaceAll(GT, ESCAPED_GT).replaceAll(LT, ESCAPED_LT);
+        }
+    }
+
+    /**
+     * Reconstitutes the original DN from the sanitized version passed in the 
proxy chain.
+     * <p>
+     * Example:
+     * <p>
+     * {@code alopresto\>\<proxy1} -> {@code alopresto><proxy1}
+     *
+     * @param sanitizedDn the sanitized DN
+     * @return the original DN
+     */
+    private static String unsanitizeDn(String sanitizedDn) {
+        if (StringUtils.isEmpty(sanitizedDn)) {
+            return sanitizedDn;
+        } else {
+            return sanitizedDn.replaceAll(ESCAPED_GT, 
GT).replaceAll(ESCAPED_LT, LT);
+        }
+    }
+
+    private X509AuthenticationRequestToken getX509Request(final String 
proxyChain, final String identity) {
+        return new X509AuthenticationRequestToken(proxyChain, extractor, new 
X509Certificate[]{getX509Certificate(identity)}, "");
+    }
+
+    private X509Certificate getX509Certificate(final String identity) {
+        final X509Certificate certificate = mock(X509Certificate.class);
+        when(certificate.getSubjectDN()).then(invocation -> {
+            final Principal principal = mock(Principal.class);
+            when(principal.getName()).thenReturn(identity);
+            return principal;
+        });
+        return certificate;
+    }
+
+}
\ No newline at end of file

Reply via email to