Repository: nifi-registry
Updated Branches:
  refs/heads/master 63ddf4129 -> ef8ba127c


http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
index 49c17ea..4401a15 100644
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/jwt/JwtService.java
@@ -28,17 +28,20 @@ import io.jsonwebtoken.SignatureException;
 import io.jsonwebtoken.SigningKeyResolverAdapter;
 import io.jsonwebtoken.UnsupportedJwtException;
 import org.apache.commons.lang3.StringUtils;
-
 import org.apache.nifi.registry.exception.AdministrationException;
+import org.apache.nifi.registry.security.authentication.AuthenticationResponse;
 import org.apache.nifi.registry.security.key.Key;
 import org.apache.nifi.registry.security.key.KeyService;
-import 
org.apache.nifi.registry.web.security.authentication.token.LoginAuthenticationToken;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
 import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
 
+// TODO, look into replacing this JwtService service with Apache Licensed JJWT 
library
 @Service
 public class JwtService {
 
@@ -50,6 +53,7 @@ public class JwtService {
 
     private final KeyService keyService;
 
+    @Autowired
     public JwtService(final KeyService keyService) {
         this.keyService = keyService;
     }
@@ -68,7 +72,7 @@ public class JwtService {
                 throw new JwtException("No subject available in token");
             }
 
-            // TODO: Validate issuer against active registry?
+            // TODO: Validate issuer against active IdentityProvider?
             if (StringUtils.isEmpty(jws.getBody().getIssuer())) {
                 throw new JwtException("No issuer available in token");
             }
@@ -110,46 +114,48 @@ public class JwtService {
     /**
      * Generates a signed JWT token from the provided (Spring Security) login 
authentication token.
      *
-     * @param authenticationToken an instance of the Spring Security token 
after login credentials have been verified against the respective information 
source
+     * @param authenticationResponse an instance of the Spring Security token 
after login credentials have been verified against the respective information 
source
      * @return a signed JWT containing the user identity and the identity 
provider, Base64-encoded
      * @throws JwtException if there is a problem generating the signed token
      */
-    public String generateSignedToken(final LoginAuthenticationToken 
authenticationToken) throws JwtException {
-        if (authenticationToken == null) {
+    public String generateSignedToken(final AuthenticationResponse 
authenticationResponse) throws JwtException {
+        if (authenticationResponse == null) {
             throw new IllegalArgumentException("Cannot generate a JWT for a 
null authentication token");
         }
 
         // Set expiration from the token
-        final Calendar expiration = Calendar.getInstance();
-        expiration.setTimeInMillis(authenticationToken.getExpiration());
+        final Calendar now = Calendar.getInstance();
+        long expirationMillisRelativeToNow = 
validateTokenExpiration(authenticationResponse.getExpiration(), 
authenticationResponse.getIdentity());
+        long expirationMillis = now.getTimeInMillis() + 
expirationMillisRelativeToNow;
+        final Calendar expiration = new 
Calendar.Builder().setInstant(expirationMillis).build();
 
-        final Object principal = authenticationToken.getPrincipal();
+        final Object principal = authenticationResponse.getIdentity();
         if (principal == null || StringUtils.isEmpty(principal.toString())) {
-            final String errorMessage = "Cannot generate a JWT for a token 
with an empty identity issued by " + authenticationToken.getIssuer();
+            final String errorMessage = "Cannot generate a JWT for a token 
with an empty identity issued by " + authenticationResponse.getIssuer();
             logger.error(errorMessage);
             throw new JwtException(errorMessage);
         }
 
         // Create a JWT with the specified authentication
         final String identity = principal.toString();
-        final String username = authenticationToken.getName();
+        final String username = authenticationResponse.getUsername();
 
         try {
             // Get/create the key for this user
             final Key key = keyService.getOrCreateKey(identity);
             final byte[] keyBytes = 
key.getKey().getBytes(StandardCharsets.UTF_8);
 
-            logger.trace("Generating JWT for " + authenticationToken);
+            logger.trace("Generating JWT for " + 
describe(authenticationResponse));
 
             // TODO: Implement "jti" claim with nonce to prevent replay 
attacks and allow blacklisting of revoked tokens
             // Build the token
             return Jwts.builder().setSubject(identity)
-                    .setIssuer(authenticationToken.getIssuer())
-                    .setAudience(authenticationToken.getIssuer())
+                    .setIssuer(authenticationResponse.getIssuer())
+                    .setAudience(authenticationResponse.getIssuer())
                     .claim(USERNAME_CLAIM, username)
                     .claim(KEY_ID_CLAIM, key.getId())
+                    .setIssuedAt(now.getTime())
                     .setExpiration(expiration.getTime())
-                    .setIssuedAt(Calendar.getInstance().getTime())
                     .signWith(SIGNATURE_ALGORITHM, keyBytes).compact();
         } catch (NullPointerException | AdministrationException e) {
             final String errorMessage = "Could not retrieve the signing key 
for JWT for " + identity;
@@ -157,4 +163,44 @@ public class JwtService {
             throw new JwtException(errorMessage, e);
         }
     }
+
+    private long validateTokenExpiration(long proposedTokenExpiration, String 
identity) {
+        final long maxExpiration = TimeUnit.MILLISECONDS.convert(12, 
TimeUnit.HOURS);
+        final long minExpiration = TimeUnit.MILLISECONDS.convert(1, 
TimeUnit.MINUTES);
+
+        if (proposedTokenExpiration > maxExpiration) {
+            logger.warn(String.format("Max token expiration exceeded. Setting 
expiration to %s from %s for %s", maxExpiration,
+                    proposedTokenExpiration, identity));
+            proposedTokenExpiration = maxExpiration;
+        } else if (proposedTokenExpiration < minExpiration) {
+            logger.warn(String.format("Min token expiration not met. Setting 
expiration to %s from %s for %s", minExpiration,
+                    proposedTokenExpiration, identity));
+            proposedTokenExpiration = minExpiration;
+        }
+
+        return proposedTokenExpiration;
+    }
+
+    private static String describe(AuthenticationResponse 
authenticationResponse) {
+        Calendar expirationTime = Calendar.getInstance();
+        expirationTime.setTimeInMillis(authenticationResponse.getExpiration());
+        long remainingTime = expirationTime.getTimeInMillis() - 
Calendar.getInstance().getTimeInMillis();
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy 
HH:mm:ss.SSS");
+        dateFormat.setTimeZone(expirationTime.getTimeZone());
+        String expirationTimeString = 
dateFormat.format(expirationTime.getTime());
+
+        return new StringBuilder("LoginAuthenticationToken for ")
+                .append(authenticationResponse.getUsername())
+                .append(" issued by ")
+                .append(authenticationResponse.getIssuer())
+                .append(" expiring at ")
+                .append(expirationTimeString)
+                .append(" [")
+                .append(authenticationResponse.getExpiration())
+                .append(" ms, ")
+                .append(remainingTime)
+                .append(" ms remaining]")
+                .toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/LoginAuthenticationToken.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/LoginAuthenticationToken.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/LoginAuthenticationToken.java
deleted file mode 100644
index 08f0637..0000000
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/LoginAuthenticationToken.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.registry.web.security.authentication.token;
-
-import org.apache.nifi.registry.security.util.CertificateUtils;
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-
-/**
- * This is an Authentication Token for logging in. Once a user is 
authenticated, they can be issued an ID token.
- */
-public class LoginAuthenticationToken extends AbstractAuthenticationToken {
-
-    private final String identity;
-    private final String username;
-    private final long expiration;
-    private final String issuer;
-
-    /**
-     * Creates a representation of the authentication token for a user.
-     *
-     * @param identity   The unique identifier for this user
-     * @param expiration The relative time to expiration in milliseconds
-     * @param issuer     The IdentityProvider implementation that generated 
this token
-     */
-    public LoginAuthenticationToken(final String identity, final long 
expiration, final String issuer) {
-        this(identity, null, expiration, issuer);
-    }
-
-    /**
-     * Creates a representation of the authentication token for a user.
-     *
-     * @param identity   The unique identifier for this user (cannot be null 
or empty)
-     * @param username   The preferred username for this user
-     * @param expiration The relative time to expiration in milliseconds
-     * @param issuer     The IdentityProvider implementation that generated 
this token
-     */
-    public LoginAuthenticationToken(final String identity, final String 
username, final long expiration, final String issuer) {
-        super(null);
-        setAuthenticated(true);
-        this.identity = identity;
-        this.username = username;
-        this.issuer = issuer;
-        Calendar now = Calendar.getInstance();
-        this.expiration = now.getTimeInMillis() + expiration;
-    }
-
-    @Override
-    public Object getCredentials() {
-        return null;
-    }
-
-    @Override
-    public Object getPrincipal() {
-        return identity;
-    }
-
-    /**
-     * Returns the expiration instant in milliseconds. This value is an 
absolute point in time (i.e. Nov
-     * 16, 2015 11:30:00.000 GMT), not a relative time (i.e. 60 minutes). It 
is calculated by adding the
-     * relative expiration from the constructor to the timestamp at object 
creation.
-     *
-     * @return the expiration in millis
-     */
-    public long getExpiration() {
-        return expiration;
-    }
-
-    public String getIssuer() {
-        return issuer;
-    }
-
-    @Override
-    public String getName() {
-        if (username == null) {
-            // if the username is a DN this will extract the username or CN... 
if not will return what was passed
-            return CertificateUtils.extractUsername(identity);
-        } else {
-            return username;
-        }
-    }
-
-    @Override
-    public String toString() {
-        Calendar expirationTime = Calendar.getInstance();
-        expirationTime.setTimeInMillis(getExpiration());
-        long remainingTime = expirationTime.getTimeInMillis() - 
Calendar.getInstance().getTimeInMillis();
-
-        SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy 
HH:mm:ss.SSS");
-        dateFormat.setTimeZone(expirationTime.getTimeZone());
-        String expirationTimeString = 
dateFormat.format(expirationTime.getTime());
-
-        return new StringBuilder("LoginAuthenticationToken for ")
-                .append(getName())
-                .append(" issued by ")
-                .append(getIssuer())
-                .append(" expiring at ")
-                .append(expirationTimeString)
-                .append(" [")
-                .append(getExpiration())
-                .append(" ms, ")
-                .append(remainingTime)
-                .append(" ms remaining]")
-                .toString();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/NiFiAuthenticationToken.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/NiFiAuthenticationToken.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/NiFiAuthenticationToken.java
deleted file mode 100644
index 19e56c5..0000000
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/token/NiFiAuthenticationToken.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.registry.web.security.authentication.token;
-
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.core.userdetails.UserDetails;
-
-/**
- * An authentication token that represents an Authenticated and Authorized 
user of the NiFi Apis. The authorities are based off the specified UserDetails.
- */
-public class NiFiAuthenticationToken extends AbstractAuthenticationToken {
-
-    final UserDetails nifiUserDetails;
-
-    public NiFiAuthenticationToken(final UserDetails nifiUserDetails) {
-        super(nifiUserDetails.getAuthorities());
-        super.setAuthenticated(true);
-        setDetails(nifiUserDetails);
-        this.nifiUserDetails = nifiUserDetails;
-    }
-
-    @Override
-    public Object getCredentials() {
-        return nifiUserDetails.getPassword();
-    }
-
-    @Override
-    public Object getPrincipal() {
-        return nifiUserDetails;
-    }
-
-    @Override
-    public final void setAuthenticated(boolean authenticated) {
-        throw new IllegalArgumentException("Cannot change the authenticated 
state.");
-    }
-
-    @Override
-    public String toString() {
-        return nifiUserDetails.getUsername();
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationFilter.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationFilter.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationFilter.java
deleted file mode 100644
index fa0fce2..0000000
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationFilter.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.registry.web.security.authentication.x509;
-
-import 
org.apache.nifi.registry.web.security.authentication.NiFiAuthenticationFilter;
-import 
org.apache.nifi.registry.web.security.authentication.ProxiedEntitiesUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.core.Authentication;
-import 
org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
-
-import javax.servlet.http.HttpServletRequest;
-import java.security.cert.X509Certificate;
-
-/**
- * Custom X509 filter that will inspect the HTTP headers for a proxied user 
before extracting the user details from the client certificate.
- */
-public class X509AuthenticationFilter extends NiFiAuthenticationFilter {
-
-    private static final Logger logger = 
LoggerFactory.getLogger(X509AuthenticationFilter.class);
-
-    private X509CertificateExtractor certificateExtractor;
-    private X509PrincipalExtractor principalExtractor;
-
-    @Override
-    public Authentication attemptAuthentication(final HttpServletRequest 
request) {
-        // only suppport x509 login when running securely
-        if (!request.isSecure()) {
-            return null;
-        }
-
-        // look for a client certificate
-        final X509Certificate[] certificates = 
certificateExtractor.extractClientCertificate(request);
-        if (certificates == null) {
-            return null;
-        }
-
-        return new 
X509AuthenticationRequestToken(request.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN),
 principalExtractor, certificates, request.getRemoteAddr());
-    }
-
-    /* setters */
-    public void setCertificateExtractor(X509CertificateExtractor 
certificateExtractor) {
-        this.certificateExtractor = certificateExtractor;
-    }
-
-    public void setPrincipalExtractor(X509PrincipalExtractor 
principalExtractor) {
-        this.principalExtractor = principalExtractor;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationProvider.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationProvider.java
deleted file mode 100644
index 3e935a2..0000000
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationProvider.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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.registry.web.security.authentication.x509;
-
-import org.apache.commons.lang3.StringUtils;
-import 
org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
-import org.apache.nifi.registry.security.authorization.Authorizer;
-import org.apache.nifi.registry.security.authorization.RequestAction;
-import org.apache.nifi.registry.security.authorization.Resource;
-import org.apache.nifi.registry.security.authorization.UserContextKeys;
-import org.apache.nifi.registry.security.authorization.resource.Authorizable;
-import 
org.apache.nifi.registry.security.authorization.resource.ResourceFactory;
-import org.apache.nifi.registry.security.authorization.user.NiFiUser;
-import org.apache.nifi.registry.security.authorization.user.NiFiUserDetails;
-import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
-import org.apache.nifi.registry.properties.NiFiRegistryProperties;
-import org.apache.nifi.registry.web.response.AuthenticationResponse;
-import 
org.apache.nifi.registry.web.security.authentication.exception.InvalidAuthenticationException;
-import 
org.apache.nifi.registry.web.security.authentication.NiFiAuthenticationProvider;
-import 
org.apache.nifi.registry.web.security.authentication.ProxiedEntitiesUtils;
-import 
org.apache.nifi.registry.web.security.authentication.exception.UntrustedProxyException;
-import 
org.apache.nifi.registry.web.security.authentication.token.NiFiAuthenticationToken;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-import org.springframework.stereotype.Component;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Set;
-
-@Component
-public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
-
-    private static final Authorizable PROXY_AUTHORIZABLE = new Authorizable() {
-        @Override
-        public Authorizable getParentAuthorizable() {
-            return null;
-        }
-
-        @Override
-        public Resource getResource() {
-            return ResourceFactory.getProxyResource();
-        }
-    };
-
-    private X509IdentityProvider certificateIdentityProvider;
-    private Authorizer authorizer;
-
-    @Autowired
-    public X509AuthenticationProvider(
-            final X509IdentityProvider certificateIdentityProvider,
-            final Authorizer authorizer,
-            final NiFiRegistryProperties properties) {
-        super(properties, authorizer);
-        this.certificateIdentityProvider = certificateIdentityProvider;
-        this.authorizer = authorizer;
-    }
-
-    @Override
-    public Authentication authenticate(Authentication authentication) throws 
AuthenticationException {
-        final X509AuthenticationRequestToken request = 
(X509AuthenticationRequestToken) authentication;
-
-        // attempt to authenticate if certificates were found
-        final AuthenticationResponse authenticationResponse;
-        try {
-            authenticationResponse = 
certificateIdentityProvider.authenticate(request.getCertificates());
-        } catch (final IllegalArgumentException iae) {
-            throw new InvalidAuthenticationException(iae.getMessage(), iae);
-        }
-
-        if (StringUtils.isBlank(request.getProxiedEntitiesChain())) {
-            final String mappedIdentity = 
mapIdentity(authenticationResponse.getIdentity());
-            return new NiFiAuthenticationToken(new NiFiUserDetails(
-                    new StandardNiFiUser.Builder()
-                            .identity(mappedIdentity)
-                            .groups(getUserGroups(mappedIdentity))
-                            .clientAddress(request.getClientAddress())
-                            .build()));
-        } else {
-            // build the entire proxy chain if applicable - 
<end-user><proxy1><proxy2>
-            final List<String> proxyChain = new 
ArrayList<>(ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(request.getProxiedEntitiesChain()));
-            proxyChain.add(authenticationResponse.getIdentity());
-
-            // add the chain as appropriate to each proxy
-            NiFiUser proxy = null;
-            for (final ListIterator<String> chainIter = 
proxyChain.listIterator(proxyChain.size()); chainIter.hasPrevious(); ) {
-                String identity = chainIter.previous();
-
-                // determine if the user is anonymous
-                final boolean isAnonymous = StringUtils.isBlank(identity);
-                if (isAnonymous) {
-                    identity = StandardNiFiUser.ANONYMOUS_IDENTITY;
-                } else {
-                    identity = mapIdentity(identity);
-                }
-
-                final Set<String> groups = getUserGroups(identity);
-
-                // Only set the client address for client making the request 
because we don't know the clientAddress of the proxied entities
-                String clientAddress = (proxy == null) ? 
request.getClientAddress() : null;
-                proxy = createUser(identity, groups, proxy, clientAddress, 
isAnonymous);
-
-                if (chainIter.hasPrevious()) {
-                    try {
-                        PROXY_AUTHORIZABLE.authorize(authorizer, 
RequestAction.WRITE, proxy);
-                    } catch (final AccessDeniedException e) {
-                        throw new 
UntrustedProxyException(String.format("Untrusted proxy %s", identity));
-                    }
-                }
-            }
-
-            return new NiFiAuthenticationToken(new NiFiUserDetails(proxy));
-        }
-    }
-
-    /**
-     * Returns a regular user populated with the provided values, or if the 
user should be anonymous, a well-formed instance of the anonymous user with the 
provided values.
-     *
-     * @param identity      the user's identity
-     * @param chain         the proxied entities
-     * @param clientAddress the requesting IP address
-     * @param isAnonymous   if true, an anonymous user will be returned 
(identity will be ignored)
-     * @return the populated user
-     */
-    protected static NiFiUser createUser(String identity, Set<String> groups, 
NiFiUser chain, String clientAddress, boolean isAnonymous) {
-        if (isAnonymous) {
-            return StandardNiFiUser.populateAnonymousUser(chain, 
clientAddress);
-        } else {
-            return new 
StandardNiFiUser.Builder().identity(identity).groups(groups).chain(chain).clientAddress(clientAddress).build();
-        }
-    }
-
-    private Map<String, String> getUserContext(final 
X509AuthenticationRequestToken request) {
-        final Map<String, String> userContext;
-        if (!StringUtils.isBlank(request.getClientAddress())) {
-            userContext = new HashMap<>();
-            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), 
request.getClientAddress());
-        } else {
-            userContext = null;
-        }
-        return userContext;
-    }
-
-    @Override
-    public boolean supports(Class<?> authentication) {
-        return 
X509AuthenticationRequestToken.class.isAssignableFrom(authentication);
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationRequestToken.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationRequestToken.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationRequestToken.java
deleted file mode 100644
index d5aca23..0000000
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509AuthenticationRequestToken.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.registry.web.security.authentication.x509;
-
-import org.apache.commons.lang3.StringUtils;
-import 
org.apache.nifi.registry.web.security.authentication.NiFiAuthenticationRequestToken;
-import 
org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
-
-import java.security.cert.X509Certificate;
-
-/**
- * This is an authentication request with a given JWT token.
- */
-public class X509AuthenticationRequestToken extends 
NiFiAuthenticationRequestToken {
-
-    private final String proxiedEntitiesChain;
-    private final X509PrincipalExtractor principalExtractor;
-    private final X509Certificate[] certificates;
-
-    /**
-     * Creates a representation of the jwt authentication request for a user.
-     *
-     * @param proxiedEntitiesChain   The http servlet request
-     * @param certificates  The certificate chain
-     */
-    public X509AuthenticationRequestToken(final String proxiedEntitiesChain, 
final X509PrincipalExtractor principalExtractor, final X509Certificate[] 
certificates, final String clientAddress) {
-        super(clientAddress);
-        setAuthenticated(false);
-        this.proxiedEntitiesChain = proxiedEntitiesChain;
-        this.principalExtractor = principalExtractor;
-        this.certificates = certificates;
-    }
-
-    @Override
-    public Object getCredentials() {
-        return null;
-    }
-
-    @Override
-    public Object getPrincipal() {
-        if (StringUtils.isBlank(proxiedEntitiesChain)) {
-            return principalExtractor.extractPrincipal(certificates[0]);
-        } else {
-            return String.format("%s<%s>", proxiedEntitiesChain, 
principalExtractor.extractPrincipal(certificates[0]));
-        }
-    }
-
-    public String getProxiedEntitiesChain() {
-        return proxiedEntitiesChain;
-    }
-
-    public X509Certificate[] getCertificates() {
-        return certificates;
-    }
-
-    @Override
-    public String toString() {
-        return getName();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509CertificateValidator.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509CertificateValidator.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509CertificateValidator.java
deleted file mode 100644
index d748b93..0000000
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509CertificateValidator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.registry.web.security.authentication.x509;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
-
-import java.security.cert.CertificateExpiredException;
-import java.security.cert.CertificateNotYetValidException;
-import java.security.cert.X509Certificate;
-
-/**
- * Extracts client certificates from Http requests.
- */
-@Component
-public class X509CertificateValidator {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    /**
-     * Extract the client certificate from the specified HttpServletRequest or 
null if none is specified.
-     *
-     * @param certificates the client certificates
-     * @throws CertificateExpiredException cert is expired
-     * @throws CertificateNotYetValidException cert is not yet valid
-     */
-    public void validateClientCertificate(final X509Certificate[] certificates)
-            throws CertificateExpiredException, 
CertificateNotYetValidException {
-
-        // ensure the cert is valid
-        certificates[0].checkValidity();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
new file mode 100644
index 0000000..d4be5e9
--- /dev/null
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityAuthenticationProvider.java
@@ -0,0 +1,131 @@
+/*
+ * 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.registry.web.security.authentication.x509;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.security.authentication.AuthenticationRequest;
+import 
org.apache.nifi.registry.web.security.authentication.AuthenticationRequestToken;
+import org.apache.nifi.registry.security.authentication.AuthenticationResponse;
+import org.apache.nifi.registry.security.authentication.IdentityProvider;
+import 
org.apache.nifi.registry.web.security.authentication.IdentityAuthenticationProvider;
+import 
org.apache.nifi.registry.web.security.authentication.AuthenticationSuccessToken;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.Resource;
+import 
org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
+import org.apache.nifi.registry.security.authorization.resource.Authorizable;
+import 
org.apache.nifi.registry.security.authorization.resource.ResourceFactory;
+import org.apache.nifi.registry.security.authorization.user.NiFiUser;
+import org.apache.nifi.registry.security.authorization.user.NiFiUserDetails;
+import org.apache.nifi.registry.security.authorization.user.StandardNiFiUser;
+import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils;
+import 
org.apache.nifi.registry.web.security.authentication.exception.UntrustedProxyException;
+
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+public class X509IdentityAuthenticationProvider extends 
IdentityAuthenticationProvider {
+
+    private static final Authorizable PROXY_AUTHORIZABLE = new Authorizable() {
+        @Override
+        public Authorizable getParentAuthorizable() {
+            return null;
+        }
+
+        @Override
+        public Resource getResource() {
+            return ResourceFactory.getProxyResource();
+        }
+    };
+
+    public X509IdentityAuthenticationProvider(NiFiRegistryProperties 
properties, Authorizer authorizer, IdentityProvider identityProvider) {
+        super(properties, authorizer, identityProvider);
+    }
+
+    @Override
+    protected AuthenticationSuccessToken buildAuthenticatedToken(
+            AuthenticationRequestToken requestToken,
+            AuthenticationResponse response) {
+
+        AuthenticationRequest authenticationRequest = 
requestToken.getAuthenticationRequest();
+
+        String proxiedEntitiesChain = authenticationRequest.getDetails() != 
null
+                ? (String)authenticationRequest.getDetails()
+                : null;
+
+        if (StringUtils.isBlank(proxiedEntitiesChain)) {
+            return super.buildAuthenticatedToken(requestToken, response);
+        }
+
+        // build the entire proxy chain if applicable - 
<end-user><proxy1><proxy2>
+        final List<String> proxyChain = 
ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(proxiedEntitiesChain);
+        proxyChain.add(response.getIdentity());
+
+        // add the chain as appropriate to each proxy
+        NiFiUser proxy = null;
+        for (final ListIterator<String> chainIter = 
proxyChain.listIterator(proxyChain.size()); chainIter.hasPrevious(); ) {
+            String identity = chainIter.previous();
+
+            // determine if the user is anonymous
+            final boolean isAnonymous = StringUtils.isBlank(identity);
+            if (isAnonymous) {
+                identity = StandardNiFiUser.ANONYMOUS_IDENTITY;
+            } else {
+                identity = mapIdentity(identity);
+            }
+
+            final Set<String> groups = getUserGroups(identity);
+
+            // Only set the client address for client making the request 
because we don't know the clientAddress of the proxied entities
+            String clientAddress = (proxy == null) ? 
requestToken.getClientAddress() : null;
+            proxy = createUser(identity, groups, proxy, clientAddress, 
isAnonymous);
+
+            if (chainIter.hasPrevious()) {
+                try {
+                    PROXY_AUTHORIZABLE.authorize(authorizer, 
RequestAction.WRITE, proxy);
+                } catch (final AccessDeniedException e) {
+                    throw new UntrustedProxyException(String.format("Untrusted 
proxy %s", identity));
+                }
+            }
+        }
+
+        return new AuthenticationSuccessToken(new NiFiUserDetails(proxy));
+
+    }
+
+    /**
+     * Returns a regular user populated with the provided values, or if the 
user should be anonymous, a well-formed instance of the anonymous user with the 
provided values.
+     *
+     * @param identity      the user's identity
+     * @param chain         the proxied entities
+     * @param clientAddress the requesting IP address
+     * @param isAnonymous   if true, an anonymous user will be returned 
(identity will be ignored)
+     * @return the populated user
+     */
+    private static NiFiUser createUser(String identity, Set<String> groups, 
NiFiUser chain, String clientAddress, boolean isAnonymous) {
+        if (isAnonymous) {
+            return StandardNiFiUser.populateAnonymousUser(chain, 
clientAddress);
+        } else {
+            return new 
StandardNiFiUser.Builder().identity(identity).groups(groups).chain(chain).clientAddress(clientAddress).build();
+        }
+    }
+
+
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java
 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java
index 692b318..9631efc 100644
--- 
a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java
+++ 
b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/security/authentication/x509/X509IdentityProvider.java
@@ -16,13 +16,22 @@
  */
 package org.apache.nifi.registry.web.security.authentication.x509;
 
-import org.apache.nifi.registry.web.response.AuthenticationResponse;
+import org.apache.nifi.registry.security.authentication.AuthenticationRequest;
+import org.apache.nifi.registry.security.authentication.AuthenticationResponse;
+import org.apache.nifi.registry.security.authentication.IdentityProvider;
+import 
org.apache.nifi.registry.security.authentication.IdentityProviderConfigurationContext;
+import org.apache.nifi.registry.security.authentication.IdentityProviderUsage;
+import 
org.apache.nifi.registry.security.authentication.exception.InvalidCredentialsException;
+import 
org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import 
org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
+import org.apache.nifi.registry.security.util.ProxiedEntitiesUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import 
org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
 import org.springframework.stereotype.Component;
 
+import javax.servlet.http.HttpServletRequest;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
@@ -32,73 +41,126 @@ import java.util.concurrent.TimeUnit;
  * Identity provider for extract the authenticating a ServletRequest with a 
X509Certificate.
  */
 @Component
-public class X509IdentityProvider {
+public class X509IdentityProvider implements IdentityProvider {
 
     private static final Logger logger = 
LoggerFactory.getLogger(X509IdentityProvider.class);
 
-    private final String issuer = getClass().getSimpleName();
+    private static final String issuer = 
X509IdentityProvider.class.getSimpleName();
+
+    private static final long expiration = TimeUnit.MILLISECONDS.convert(12, 
TimeUnit.HOURS);
+
+    private static final IdentityProviderUsage usage = new 
IdentityProviderUsage() {
+        @Override
+        public String getText() {
+            return "The client must connect over HTTPS and must provide a 
client certificate during the TLS handshake. " +
+                    "Additionally, the client may declare itself a proxy for 
another user identity by populating the " +
+                    ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN + " HTTP header 
field with a value of the format " +
+                    
"'<end-user-identity><proxy1-identity><proxy2-identity>...<proxyN-identity>'" +
+                    "for all identities in the chain prior to this client. If 
the " + ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN +
+                    " header is present in the request, this client's identity 
will be extracted from the client certificate " +
+                    "used for TLS and added to the end of the chain, and then 
the entire chain will be authorized. Each proxy " +
+                    "will be authorized to have 'write' access to '/proxy', 
and the originating user identity will be " +
+                    "authorized for access to the resource being accessed in 
the request.";
+        }
+    };
 
-    private X509CertificateValidator certificateValidator;
     private X509PrincipalExtractor principalExtractor;
+    private X509CertificateExtractor certificateExtractor;
 
     @Autowired
-    public X509IdentityProvider(X509CertificateValidator certificateValidator, 
X509PrincipalExtractor principalExtractor) {
-        this.certificateValidator = certificateValidator;
+    public X509IdentityProvider(X509PrincipalExtractor principalExtractor, 
X509CertificateExtractor certificateExtractor) {
         this.principalExtractor = principalExtractor;
+        this.certificateExtractor = certificateExtractor;
+    }
+
+    @Override
+    public IdentityProviderUsage getUsageInstructions() {
+        return usage;
     }
 
     /**
-     * Authenticates the specified request by checking certificate validity.
+     * Extracts certificate-based credentials from an {@link 
HttpServletRequest}.
+     *
+     * The resulting {@link AuthenticationRequest} will be populated as:
+     *  - username: principal DN from first client cert
+     *  - credentials: first client certificate (X509Certificate)
+     *  - details: proxied-entities chain (String)
      *
-     * @param certificates the client certificates
-     * @return an authentication response
-     * @throws IllegalArgumentException the request did not contain a valid 
certificate (or no certificate)
+     * @param servletRequest the {@link HttpServletRequest} request that may 
contain credentials understood by this IdentityProvider
+     * @return a populated AuthenticationRequest or null if the credentials 
could not be found.
      */
-    public AuthenticationResponse authenticate(final X509Certificate[] 
certificates) throws IllegalArgumentException {
-        // ensure the cert was found
+    @Override
+    public AuthenticationRequest extractCredentials(HttpServletRequest 
servletRequest) {
+
+        // only support x509 login when running securely
+        if (!servletRequest.isSecure()) {
+            return null;
+        }
+
+        // look for a client certificate
+        final X509Certificate[] certificates = 
certificateExtractor.extractClientCertificate(servletRequest);
         if (certificates == null || certificates.length == 0) {
-            throw new IllegalArgumentException("The specified request does not 
contain a client certificate.");
+            return null;
         }
 
         // extract the principal
         final Object certificatePrincipal = 
principalExtractor.extractPrincipal(certificates[0]);
         final String principal = certificatePrincipal.toString();
 
+        // extract the proxiedEntitiesChain header value from the 
servletRequest
+        String proxiedEntitiesChainHeader = 
servletRequest.getHeader(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN);
+
+        return new AuthenticationRequest(principal, certificates[0], 
proxiedEntitiesChainHeader);
+
+    }
+
+    /**
+     * For a given {@link AuthenticationRequest}, this validates the client 
certificate and creates a populated {@link AuthenticationResponse}.
+     *
+     * The {@link AuthenticationRequest} authenticationRequest paramenter is 
expected to be populated as:
+     *  - username: principal DN from first client cert
+     *  - credentials: first client certificate (X509Certificate)
+     *  - details: proxied-entities chain (String)
+     *
+     * @param authenticationRequest the request, containing identity claim 
credentials for the IdentityProvider to authenticate and determine an identity
+     */
+    @Override
+    public AuthenticationResponse authenticate(AuthenticationRequest 
authenticationRequest) throws InvalidCredentialsException {
+
+        if (authenticationRequest == null || 
authenticationRequest.getUsername() == null) {
+            return null;
+        }
+
+        String principal = authenticationRequest.getUsername();
+
         try {
-            certificateValidator.validateClientCertificate(certificates);
+            X509Certificate clientCertificate = 
(X509Certificate)authenticationRequest.getCredentials();
+            validateClientCertificate(clientCertificate);
         } catch (CertificateExpiredException cee) {
             final String message = String.format("Client certificate for (%s) 
is expired.", principal);
-            logger.info(message, cee);
-            if (logger.isDebugEnabled()) {
-                logger.debug("", cee);
-            }
-            throw new IllegalArgumentException(message, cee);
+            logger.warn(message, cee);
+            throw new InvalidCredentialsException(message, cee);
         } catch (CertificateNotYetValidException cnyve) {
             final String message = String.format("Client certificate for (%s) 
is not yet valid.", principal);
-            logger.info(message, cnyve);
-            if (logger.isDebugEnabled()) {
-                logger.debug("", cnyve);
-            }
-            throw new IllegalArgumentException(message, cnyve);
+            logger.warn(message, cnyve);
+            throw new InvalidCredentialsException(message, cnyve);
         } catch (final Exception e) {
-            logger.info(e.getMessage());
-            if (logger.isDebugEnabled()) {
-                logger.debug("", e);
-            }
-            throw new IllegalArgumentException(e.getMessage(), e);
+            logger.warn(e.getMessage(), e);
         }
 
         // build the authentication response
-        return new AuthenticationResponse(principal, principal, 
TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS), issuer);
+        return new AuthenticationResponse(principal, principal, expiration, 
issuer);
     }
 
-    /* setters */
-    public void setCertificateValidator(X509CertificateValidator 
certificateValidator) {
-        this.certificateValidator = certificateValidator;
-    }
+    @Override
+    public void onConfigured(IdentityProviderConfigurationContext 
configurationContext) throws SecurityProviderCreationException {}
 
-    public void setPrincipalExtractor(X509PrincipalExtractor 
principalExtractor) {
-        this.principalExtractor = principalExtractor;
+    @Override
+    public void preDestruction() throws SecurityProviderDestructionException {}
+
+
+    private void validateClientCertificate(X509Certificate certificate) throws 
CertificateExpiredException, CertificateNotYetValidException {
+        certificate.checkValidity();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/main/xsd/identity-providers.xsd
----------------------------------------------------------------------
diff --git a/nifi-registry-web-api/src/main/xsd/identity-providers.xsd 
b/nifi-registry-web-api/src/main/xsd/identity-providers.xsd
deleted file mode 100644
index bcca014..0000000
--- a/nifi-registry-web-api/src/main/xsd/identity-providers.xsd
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0"?>
-<!--
-  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.
--->
-<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema";>
-    <!-- role -->
-    <xs:complexType name="Provider">
-        <xs:sequence>
-            <xs:element name="identifier" type="NonEmptyStringType"/>
-            <xs:element name="class" type="NonEmptyStringType"/>
-            <xs:element name="property" type="Property" minOccurs="0" 
maxOccurs="unbounded" />
-        </xs:sequence>
-    </xs:complexType>
-
-    <!-- Name/Value properties-->
-    <xs:complexType name="Property">
-        <xs:simpleContent>
-            <xs:extension base="xs:string">
-                <xs:attribute name="name" type="NonEmptyStringType"/>
-                <xs:attribute name="encryption" type="xs:string"/>
-            </xs:extension>
-        </xs:simpleContent>
-    </xs:complexType>
-
-    <xs:simpleType name="NonEmptyStringType">
-        <xs:restriction base="xs:string">
-            <xs:minLength value="1"/>
-        </xs:restriction>
-    </xs:simpleType>
-
-    <!-- login identity provider -->
-    <xs:element name="identityProviders">
-        <xs:complexType>
-            <xs:sequence>
-                <xs:element name="provider" type="Provider" minOccurs="0" 
maxOccurs="unbounded"/>
-            </xs:sequence>
-        </xs:complexType>
-    </xs:element>
-</xs:schema>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/ef8ba127/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
 
b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
index d6b94c2..bdd8e11 100644
--- 
a/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
+++ 
b/nifi-registry-web-api/src/test/java/org/apache/nifi/registry/web/api/SecureLdapIT.java
@@ -45,6 +45,7 @@ import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Form;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
@@ -71,20 +72,23 @@ import static org.junit.Assert.assertTrue;
 @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = 
"classpath:db/clearDB.sql")
 public class SecureLdapIT extends IntegrationTestBase {
 
+    private static final String tokenLoginPath = "access/token/login";
+    private static final String tokenIdentityProviderPath = 
"access/token/identity-provider";
+
     @TestConfiguration
     @Profile("ITSecureLdap")
     public static class LdapTestConfiguration {
 
-        static AuthorizerFactory af;
+        static AuthorizerFactory authorizerFactory;
 
         @Primary
         @Bean
         @DependsOn({"directoryServer"}) // Can't load LdapUserGroupProvider 
until the embedded LDAP server, which creates the "directoryServer" bean, is 
running
         public static Authorizer getAuthorizer(@Autowired 
NiFiRegistryProperties properties, ExtensionManager extensionManager) {
-            if (af == null) {
-                af = new AuthorizerFactory(properties, extensionManager);
+            if (authorizerFactory == null) {
+                authorizerFactory = new AuthorizerFactory(properties, 
extensionManager);
             }
-            return af.getAuthorizer();
+            return authorizerFactory.getAuthorizer();
         }
 
     }
@@ -93,11 +97,9 @@ public class SecureLdapIT extends IntegrationTestBase {
 
     @Before
     public void generateAuthToken() {
-        final Form form = new Form()
-                .param("username", "nifiadmin")
-                .param("password", "password");
+        final Form form = encodeCredentialsForURLFormParams("nifiadmin", 
"password");
         final String token = client
-                .target(createURL("access/token"))
+                .target(createURL(tokenLoginPath))
                 .request()
                 .post(Entity.form(form), String.class);
         adminAuthToken = token;
@@ -121,12 +123,10 @@ public class SecureLdapIT extends IntegrationTestBase {
                 "\"status\":\"ACTIVE\"" +
                 "}";
 
-        // When: the /access/token endpoint is queried
-        final Form form = new Form()
-                .param("username", "nobel")
-                .param("password", "password");
+        // When: the /access/token/login endpoint is queried
+        final Form form = encodeCredentialsForURLFormParams("nobel", 
"password");
         final Response tokenResponse = client
-                .target(createURL("access/token"))
+                .target(createURL(tokenLoginPath))
                 .request()
                 .post(Entity.form(form), Response.class);
 
@@ -154,6 +154,52 @@ public class SecureLdapIT extends IntegrationTestBase {
     }
 
     @Test
+    public void testTokenGenerationWithIdentityProvider() throws Exception {
+
+        // Given: the client and server have been configured correctly for 
LDAP authentication
+        String expectedJwtPayloadJson = "{" +
+                "\"sub\":\"nobel\"," +
+                "\"preferred_username\":\"nobel\"," +
+                "\"iss\":\"LdapIdentityProvider\"," +
+                "\"aud\":\"LdapIdentityProvider\"" +
+                "}";
+        String expectedAccessStatusJson = "{" +
+                "\"identity\":\"nobel\"," +
+                "\"status\":\"ACTIVE\"" +
+                "}";
+
+        // When: the /access/token/identity-provider endpoint is queried
+        final String basicAuthCredentials = 
encodeCredentialsForBasicAuth("nobel", "password");
+        final Response tokenResponse = client
+                .target(createURL(tokenIdentityProviderPath))
+                .request()
+                .header("Authorization", "Basic " + basicAuthCredentials)
+                .post(null, Response.class);
+
+        // Then: the server returns 200 OK with an access token
+        assertEquals(201, tokenResponse.getStatus());
+        String token = tokenResponse.readEntity(String.class);
+        assertTrue(StringUtils.isNotEmpty(token));
+        String[] jwtParts = token.split("\\.");
+        assertEquals(3, jwtParts.length);
+        String jwtPayload = new String(Base64.decodeBase64(jwtParts[1]), 
"UTF-8");
+        JSONAssert.assertEquals(expectedJwtPayloadJson, jwtPayload, false);
+
+        // When: the token is returned in the Authorization header
+        final Response accessResponse = client
+                .target(createURL("access"))
+                .request()
+                .header("Authorization", "Bearer " + token)
+                .get(Response.class);
+
+        // Then: the server acknowledges the client has access
+        assertEquals(200, accessResponse.getStatus());
+        String accessStatus = accessResponse.readEntity(String.class);
+        JSONAssert.assertEquals(expectedAccessStatusJson, accessStatus, false);
+
+    }
+
+    @Test
     public void testUsers() throws Exception {
 
         // Given: the client and server have been configured correctly for 
LDAP authentication
@@ -240,15 +286,13 @@ public class SecureLdapIT extends IntegrationTestBase {
         // Given: the server has been configured with an initial admin 
"nifiadmin" and a user with no accessPolicies "nobel"
         String nobelId = getTenantIdentifierByIdentity("nobel");
         String chemistsId = getTenantIdentifierByIdentity("chemists"); // a 
group containing user "nobel"
-        final Form form = new Form()
-                .param("username", "nobel")
-                .param("password", "password");
+
+        final Form form = encodeCredentialsForURLFormParams("nobel", 
"password");
         final String nobelAuthToken = client
-                .target(createURL("access/token"))
+                .target(createURL(tokenLoginPath))
                 .request()
                 .post(Entity.form(form), String.class);
 
-
         // When: nifiadmin creates a bucket
         final Bucket bucket = new Bucket();
         bucket.setName("Integration Test Bucket");
@@ -382,4 +426,15 @@ public class SecureLdapIT extends IntegrationTestBase {
         return matchedTenant != null ? matchedTenant.getIdentifier() : null;
     }
 
+    private static Form encodeCredentialsForURLFormParams(String username, 
String password) {
+        return new Form()
+                .param("username", username)
+                .param("password", password);
+    }
+
+    private static String encodeCredentialsForBasicAuth(String username, 
String password) {
+        final String credentials = username + ":" + password;
+        final String base64credentials =  new 
String(java.util.Base64.getEncoder().encode(credentials.getBytes(Charset.forName("UTF-8"))));
+        return base64credentials;
+    }
 }

Reply via email to