diff --git a/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java b/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java index 6417a05d11..33c4eb66b1 100644 --- a/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java +++ b/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java @@ -72,6 +72,8 @@ public class NiFiAuthorizationService implements UserDetailsService { throw new UntrustedProxyException("Malformed proxy chain."); } + NiFiUser proxy = null; + // process each part of the proxy chain for (final Iterator dnIter = dnList.iterator(); dnIter.hasNext();) { final String dn = dnIter.next(); @@ -88,6 +90,14 @@ public class NiFiAuthorizationService implements UserDetailsService { logger.warn(String.format("Proxy '%s' must have '%s' authority. Current authorities: %s", dn, Authority.ROLE_PROXY.toString(), StringUtils.join(user.getAuthorities(), ", "))); throw new UntrustedProxyException(String.format("Untrusted proxy '%s' must be authorized with '%s'.", dn, Authority.ROLE_PROXY.toString())); } + + // if we've already encountered a proxy, update the chain + if (proxy != null) { + user.setChain(proxy); + } + + // record this user as the proxy for the next user in the chain + proxy = user; } catch (UsernameNotFoundException unfe) { // if this proxy is a new user, conditionally create a new account automatically if (properties.getSupportNewAccountRequests()) { @@ -112,6 +122,12 @@ public class NiFiAuthorizationService implements UserDetailsService { } } else { userDetails = getNiFiUserDetails(dn); + + // if we've already encountered a proxy, update the chain + if (proxy != null) { + final NiFiUser user = userDetails.getNiFiUser(); + user.setChain(proxy); + } } } diff --git a/nar-bundles/framework-bundle/framework/web/web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java b/nar-bundles/framework-bundle/framework/web/web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java new file mode 100644 index 0000000000..057a854509 --- /dev/null +++ b/nar-bundles/framework-bundle/framework/web/web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java @@ -0,0 +1,250 @@ +/* + * 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.authorization; + +import org.apache.nifi.admin.service.AccountDisabledException; +import org.apache.nifi.admin.service.AccountNotFoundException; +import org.apache.nifi.admin.service.AccountPendingException; +import org.apache.nifi.admin.service.AdministrationException; +import org.apache.nifi.admin.service.UserService; +import org.apache.nifi.authorization.Authority; +import org.apache.nifi.user.NiFiUser; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.DnUtils; +import org.apache.nifi.web.security.UntrustedProxyException; +import org.apache.nifi.web.security.user.NiFiUserDetails; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + + +/** + * Test case for NiFiAuthorizationService. + */ +public class NiFiAuthorizationServiceTest { + + private static final String USER = "user"; + private static final String PROXY = "proxy"; + private static final String PROXY_PROXY = "proxy-proxy"; + private static final String USER_NOT_FOUND = "user-not-found"; + private static final String USER_DISABLED = "user-disabled"; + private static final String USER_PENDING = "user-pending"; + private static final String USER_ADMIN_EXCEPTION = "user-admin-exception"; + private static final String PROXY_NOT_FOUND = "proxy-not-found"; + + private NiFiAuthorizationService authorizationService; + private UserService userService; + + @Before + public void setup() throws Exception { + // mock the web security properties + final NiFiProperties properties = Mockito.mock(NiFiProperties.class); + Mockito.when(properties.getSupportNewAccountRequests()).thenReturn(Boolean.TRUE); + + userService = Mockito.mock(UserService.class); + Mockito.doReturn(null).when(userService).createPendingUserAccount(Mockito.anyString(), Mockito.anyString()); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + String dn = (String) args[0]; + + if (null != dn) { + switch (dn) { + case USER_NOT_FOUND: + case PROXY_NOT_FOUND: + throw new AccountNotFoundException(""); + case USER_DISABLED: + throw new AccountDisabledException(""); + case USER_PENDING: + throw new AccountPendingException(""); + case USER_ADMIN_EXCEPTION: + throw new AdministrationException(); + case USER: + final NiFiUser monitor = new NiFiUser(); + monitor.setDn(dn); + monitor.getAuthorities().add(Authority.ROLE_MONITOR); + return monitor; + case PROXY: + case PROXY_PROXY: + final NiFiUser proxy = new NiFiUser(); + proxy.setDn(dn); + proxy.getAuthorities().add(Authority.ROLE_PROXY); + return proxy; + } + } + + return null; + } + }).when(userService).checkAuthorization(Mockito.anyString()); + + // create the authorization service + authorizationService = new NiFiAuthorizationService(); + authorizationService.setProperties(properties); + authorizationService.setUserService(userService); + } + + /** + * Ensures the authorization service correctly handles users invalid dn chain. + * + * @throws Exception + */ + @Test(expected = UntrustedProxyException.class) + public void testInvalidDnChain() throws Exception { + authorizationService.loadUserByUsername(USER); + } + + /** + * Ensures the authorization service correctly handles account not found. + * + * @throws Exception + */ + @Test(expected = UsernameNotFoundException.class) + public void testAccountNotFound() throws Exception { + authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER_NOT_FOUND)); + } + + /** + * Ensures the authorization service correctly handles account disabled. + * + * @throws Exception + */ + @Test(expected = AccountStatusException.class) + public void testAccountDisabled() throws Exception { + authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER_DISABLED)); + } + + /** + * Ensures the authorization service correctly handles account pending. + * + * @throws Exception + */ + @Test(expected = AccountStatusException.class) + public void testAccountPending() throws Exception { + authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER_PENDING)); + } + + /** + * Ensures the authorization service correctly handles account administration exception. + * + * @throws Exception + */ + @Test(expected = AuthenticationServiceException.class) + public void testAccountAdminException() throws Exception { + authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER_ADMIN_EXCEPTION)); + } + + /** + * Tests the case when there is no proxy. + * + * @throws Exception + */ + @Test + public void testNoProxy() throws Exception { + final NiFiUserDetails details = (NiFiUserDetails) authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER)); + final NiFiUser user = details.getNiFiUser(); + + Assert.assertEquals(USER, user.getDn()); + Assert.assertNull(user.getChain()); + } + + /** + * Tests the case when the proxy does not have ROLE_PROXY. + * + * @throws Exception + */ + @Test(expected = UntrustedProxyException.class) + public void testInvalidProxy() throws Exception { + final String dnChain = DnUtils.formatProxyDn(USER) + DnUtils.formatProxyDn(USER); + authorizationService.loadUserByUsername(dnChain); + } + + /** + * Ensures the authorization service correctly handles proxy not found by attempting + * to create an account request for the proxy. + * + * @throws Exception + */ + @Test(expected = UsernameNotFoundException.class) + public void testProxyNotFound() throws Exception { + try { + final String dnChain = DnUtils.formatProxyDn(USER) + DnUtils.formatProxyDn(PROXY_NOT_FOUND); + authorizationService.loadUserByUsername(DnUtils.formatProxyDn(dnChain)); + } finally { + Mockito.verify(userService).createPendingUserAccount(Mockito.eq(PROXY_NOT_FOUND), Mockito.anyString()); + } + } + + /** + * Tests the case when there is a proxy. + * + * @throws Exception + */ + @Test + public void testProxy() throws Exception { + final String dnChain = DnUtils.formatProxyDn(USER) + DnUtils.formatProxyDn(PROXY); + final NiFiUserDetails details = (NiFiUserDetails) authorizationService.loadUserByUsername(dnChain); + final NiFiUser user = details.getNiFiUser(); + + // verify the user + Assert.assertEquals(USER, user.getDn()); + Assert.assertNotNull(user.getChain()); + + // get the proxy + final NiFiUser proxy = user.getChain(); + + // verify the proxy + Assert.assertEquals(PROXY, proxy.getDn()); + Assert.assertNull(proxy.getChain()); + } + + /** + * Tests the case when there is are multiple proxies. + * + * @throws Exception + */ + @Test + public void testProxyProxy() throws Exception { + final String dnChain = DnUtils.formatProxyDn(USER) + DnUtils.formatProxyDn(PROXY) + DnUtils.formatProxyDn(PROXY_PROXY); + final NiFiUserDetails details = (NiFiUserDetails) authorizationService.loadUserByUsername(dnChain); + final NiFiUser user = details.getNiFiUser(); + + // verify the user + Assert.assertEquals(USER, user.getDn()); + Assert.assertNotNull(user.getChain()); + + // get the proxy + NiFiUser proxy = user.getChain(); + + // verify the proxy + Assert.assertEquals(PROXY, proxy.getDn()); + Assert.assertNotNull(proxy.getChain()); + + // get the proxies proxy + proxy = proxy.getChain(); + + // verify the proxies proxy + Assert.assertEquals(PROXY_PROXY, proxy.getDn()); + Assert.assertNull(proxy.getChain()); + } +}