Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
joewitt 2014-12-23 15:40:17 -05:00
commit ed3452f527
19 changed files with 630 additions and 353 deletions

View File

@ -17,8 +17,11 @@
package org.apache.nifi.admin.service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.nifi.authorization.Authority;
import org.apache.nifi.authorization.DownloadAuthorization;
import org.apache.nifi.user.NiFiUser;
import org.apache.nifi.user.NiFiUserGroup;
@ -43,6 +46,16 @@ public interface UserService {
*/
Boolean hasPendingUserAccount();
/**
* Determines if the users in the dnChain are authorized to download content
* with the specified attributes.
*
* @param dnChain
* @param attributes
* @return
*/
DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes);
/**
* Updates a user group using the specified group comprised of the specified
* users. Returns all the users that are currently in the specified group.

View File

@ -0,0 +1,54 @@
/*
* 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.admin.service.action;
import java.util.List;
import java.util.Map;
import org.apache.nifi.admin.dao.DAOFactory;
import org.apache.nifi.admin.service.AccountNotFoundException;
import org.apache.nifi.admin.service.AdministrationException;
import org.apache.nifi.authorization.AuthorityProvider;
import org.apache.nifi.authorization.DownloadAuthorization;
import org.apache.nifi.authorization.exception.AuthorityAccessException;
import org.apache.nifi.authorization.exception.UnknownIdentityException;
/**
* Attempts to obtain authorization to download the content with the specified
* attributes for the specified user.
*/
public class AuthorizeDownloadAction implements AdministrationAction<DownloadAuthorization> {
private final List<String> dnChain;
private final Map<String, String> attributes;
public AuthorizeDownloadAction(List<String> dnChain, Map<String, String> attributes) {
this.dnChain = dnChain;
this.attributes = attributes;
}
@Override
public DownloadAuthorization execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) {
try {
return authorityProvider.authorizeDownload(dnChain, attributes);
} catch (UnknownIdentityException uie) {
throw new AccountNotFoundException(uie.getMessage(), uie);
} catch (AuthorityAccessException aae) {
throw new AdministrationException(aae.getMessage(), aae);
}
}
}

View File

@ -18,6 +18,8 @@ package org.apache.nifi.admin.service.impl;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@ -27,6 +29,7 @@ import org.apache.nifi.admin.service.AccountDisabledException;
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.admin.service.action.AuthorizeDownloadAction;
import org.apache.nifi.admin.service.action.AuthorizeUserAction;
import org.apache.nifi.admin.service.action.DeleteUserAction;
import org.apache.nifi.admin.service.action.DisableUserAction;
@ -48,6 +51,7 @@ import org.apache.nifi.admin.service.transaction.Transaction;
import org.apache.nifi.admin.service.transaction.TransactionBuilder;
import org.apache.nifi.admin.service.transaction.TransactionException;
import org.apache.nifi.authorization.Authority;
import org.apache.nifi.authorization.DownloadAuthorization;
import org.apache.nifi.user.NiFiUser;
import org.apache.nifi.user.NiFiUserGroup;
import org.apache.nifi.util.FormatUtils;
@ -440,7 +444,7 @@ public class StandardUserService implements UserService {
* modifying a user account. This method should only be invoked from within
* a write lock.
*
* @param id
* @param group
*/
@Override
public void invalidateUserGroupAccount(String group) {
@ -500,6 +504,36 @@ public class StandardUserService implements UserService {
}
}
@Override
public DownloadAuthorization authorizeDownload(final List<String> dnChain, final Map<String, String> attributes) {
Transaction transaction = null;
readLock.lock();
try {
// start the transaction
transaction = transactionBuilder.start();
// authorize the download
AuthorizeDownloadAction authorizeDownload = new AuthorizeDownloadAction(dnChain, attributes);
DownloadAuthorization downloadAuthorization = transaction.execute(authorizeDownload);
// commit the transaction
transaction.commit();
// return the authorization
return downloadAuthorization;
} catch (TransactionException | DataAccessException te) {
rollback(transaction);
throw new AdministrationException(te);
} catch (Throwable t) {
rollback(transaction);
throw t;
} finally {
closeQuietly(transaction);
readLock.unlock();
}
}
@Override
public Collection<NiFiUser> getUsers() {
Transaction transaction = null;

View File

@ -24,6 +24,7 @@ import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.XMLConstants;
@ -365,6 +366,11 @@ public class AuthorityProviderFactoryBean implements FactoryBean, ApplicationCon
public void ungroup(String group) throws AuthorityAccessException {
}
@Override
public DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes) throws UnknownIdentityException, AuthorityAccessException {
return DownloadAuthorization.approved();
}
@Override
public void initialize(AuthorityProviderInitializationContext initializationContext) throws ProviderCreationException {
}
@ -465,6 +471,13 @@ public class AuthorityProviderFactoryBean implements FactoryBean, ApplicationCon
}
}
@Override
public DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes) throws UnknownIdentityException, AuthorityAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseProvider.authorizeDownload(dnChain, attributes);
}
}
@Override
public void initialize(AuthorityProviderInitializationContext initializationContext) throws ProviderCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {

View File

@ -43,6 +43,8 @@ public class NiFiUser implements Serializable {
private AccountStatus status;
private EnumSet<Authority> authorities;
private NiFiUser chain;
/* getters / setters */
public Date getCreation() {
@ -117,6 +119,14 @@ public class NiFiUser implements Serializable {
this.lastAccessed = lastAccessed;
}
public NiFiUser getChain() {
return chain;
}
public void setChain(NiFiUser chain) {
this.chain = chain;
}
public Set<Authority> getAuthorities() {
if (authorities == null) {
authorities = EnumSet.noneOf(Authority.class);

View File

@ -1,284 +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.admin.service.impl;
import org.junit.Ignore;
/**
*
*/
@Ignore
public class NiFiAuthorizationServiceTest {
// private static final String UNKNOWN_USER_IN_CACHE_DN = "unknown-user-in-cache-dn";
// private static final String PENDING_USER_DN = "pending-user-dn";
// private static final String DISABLED_USER_DN = "disabled-user-dn";
// private static final String UNKNOWN_USER_IN_IDENTITY_PROVIDER_DN = "unknown-user-in-identity-provider-dn";
// private static final String ACCESS_EXCEPTION_IN_IDENTITY_PROVIDER_DN = "access-exception-in-identity-provider-dn";
// private static final String UNABLE_TO_UPDATE_CACHE_DN = "unable-to-update-cache-dn";
// private static final String VERIFICATION_REQUIRED_DN = "verification-required-dn";
// private static final String VERIFICATION_NOT_REQUIRED_DN = "verification-not-required-dn";
// private static final String NEW_USER_DN = "new-user-dn";
//
// private UserService userService;
// private AuthorityProvider authorityProvider;
// private UserDAO userDAO;
//
// @Before
// public void setup() throws Exception {
// // mock the web security properties
// NiFiProperties properties = Mockito.mock(NiFiProperties.class);
// Mockito.when(properties.getSupportNewAccountRequests()).thenReturn(Boolean.TRUE);
// Mockito.when(properties.getUserCredentialCacheDurationSeconds()).thenReturn(60);
//
// // mock the authority provider
//
// // mock the admin service
// userDAO = Mockito.mock(UserDAO.class);
// Mockito.doAnswer(new Answer() {
//
// @Override
// public Object answer(InvocationOnMock invocation) throws Throwable {
// Object[] args = invocation.getArguments();
// String dn = (String) args[0];
//
// NiFiUser user = null;
// switch (dn) {
// case PENDING_USER_DN:
// user = new NiFiUser();
// user.setDn(dn);
// user.setStatus(AccountStatus.PENDING);
// break;
// case DISABLED_USER_DN:
// user = new NiFiUser();
// user.setDn(dn);
// user.setStatus(AccountStatus.DISABLED);
// break;
// case UNKNOWN_USER_IN_IDENTITY_PROVIDER_DN:
// case UNABLE_TO_UPDATE_CACHE_DN:
// case ACCESS_EXCEPTION_IN_IDENTITY_PROVIDER_DN:
// user = new NiFiUser();
// user.setDn(dn);
// user.setStatus(AccountStatus.ACTIVE);
// break;
// case VERIFICATION_REQUIRED_DN: {
// Calendar calendar = Calendar.getInstance();
// calendar.add(Calendar.SECOND, -65);
// user = new NiFiUser();
// user.setDn(dn);
// user.setStatus(AccountStatus.ACTIVE);
// user.setLastVerified(calendar.getTime());
// user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_ADMIN, Authority.ROLE_DFM));
// break;
// }
// case VERIFICATION_NOT_REQUIRED_DN: {
// Calendar calendar = Calendar.getInstance();
// calendar.add(Calendar.SECOND, -5);
// user = new NiFiUser();
// user.setDn(dn);
// user.setStatus(AccountStatus.ACTIVE);
// user.setLastVerified(calendar.getTime());
// user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_ADMIN, Authority.ROLE_DFM));
// break;
// }
// }
// return user;
// }
// }).when(userDAO).getUser(Mockito.anyString());
// Mockito.doAnswer(new Answer() {
//
// @Override
// public Object answer(InvocationOnMock invocation) throws Throwable {
// Object[] args = invocation.getArguments();
// NiFiUser user = (NiFiUser) args[0];
//
// if (UNABLE_TO_UPDATE_CACHE_DN.equals(user.getDn())) {
// throw new AdministrationException();
// }
// return user;
// }
// }).when(userDAO).updateUser(Mockito.any(NiFiUser.class));
// Mockito.doNothing().when(userDAO).createUser(Mockito.any(NiFiUser.class));
//
// // mock the authority provider
// authorityProvider = Mockito.mock(AuthorityProvider.class);
// Mockito.doAnswer(new Answer() {
//
// @Override
// public Object answer(InvocationOnMock invocation) throws Throwable {
// Object[] args = invocation.getArguments();
// String dn = (String) args[0];
//
// boolean hasDn = false;
// if (VERIFICATION_REQUIRED_DN.equals(dn) || NEW_USER_DN.equals(dn)) {
// hasDn = true;
// }
// return hasDn;
// }
// }).when(authorityProvider).doesDnExist(Mockito.anyString());
// Mockito.doAnswer(new Answer() {
//
// @Override
// public Object answer(InvocationOnMock invocation) throws Throwable {
// Object[] args = invocation.getArguments();
// String dn = (String) args[0];
//
// Set<String> authorities = null;
// switch (dn) {
// case VERIFICATION_REQUIRED_DN:
// case NEW_USER_DN:
// authorities = new HashSet<>();
// authorities.add("ROLE_MONITOR");
// break;
// case DISABLED_USER_DN:
// throw new UnknownIdentityException("Unable to find user");
// }
// return authorities;
// }
// }).when(authorityProvider).getAuthorities(Mockito.anyString());
//
// // create an instance of the authorization service
// userService = new UserServiceImpl();
// ((UserServiceImpl) userService).setAuthorityProvider(authorityProvider);
// ((UserServiceImpl) userService).set(authorityProvider);
//
//// authorizationService.setIdentityProvider(identityProvider);
//// authorizationService.setAuthorityProvider(authorityProvider);
//// authorizationService.setProperties(properties);
// }
//
// /**
// * Ensures the authorization service correctly handles users who are
// * unknown.
// *
// * @throws Exception
// */
// @Test(expected = org.springframework.security.core.userdetails.UsernameNotFoundException.class)
// public void testUnknownUserInCache() throws Exception {
// authorizationService.loadUserByUsername(WebUtils.formatProxyDn(UNKNOWN_USER_IN_CACHE_DN));
// }
//
// /**
// * Ensures the authorization service correctly handles users whose accounts
// * are PENDING.
// *
// * @throws Exception
// */
// @Test(expected = nifi.admin.service.AccountPendingException.class)
// public void testPendingUser() throws Exception {
// authorizationService.loadUserByUsername(WebUtils.formatProxyDn(PENDING_USER_DN));
// }
//
// /**
// * Ensures the authorization service correctly handles users whose accounts
// * are DISABLED.
// *
// * @throws Exception
// */
// @Test(expected = org.springframework.security.authentication.DisabledException.class)
// public void testDisabledUser() throws Exception {
// authorizationService.loadUserByUsername(WebUtils.formatProxyDn(DISABLED_USER_DN));
// }
//
// /**
// * Ensures the authorization service correctly handles users whose are in
// * the cache but have been removed from the identity provider.
// *
// * @throws Exception
// */
// @Test(expected = org.springframework.security.authentication.DisabledException.class)
// public void testUnknownUserInIdentityProvider() throws Exception {
// authorizationService.loadUserByUsername(WebUtils.formatProxyDn(UNKNOWN_USER_IN_IDENTITY_PROVIDER_DN));
// }
//
// /**
// * Ensures the authorization service correctly handles cases when the cache
// * is unable to be updated.
// *
// * @throws Exception
// */
// @Test(expected = org.springframework.security.authentication.AuthenticationServiceException.class)
// public void testUnableToUpdateCache() throws Exception {
// authorizationService.loadUserByUsername(WebUtils.formatProxyDn(UNABLE_TO_UPDATE_CACHE_DN));
// }
//
// /**
// * Ensures the authorization service correctly handles cases when the
// * identity provider has an access exception.
// *
// * @throws Exception
// */
// @Test(expected = org.springframework.security.authentication.AuthenticationServiceException.class)
// public void testUnableToAccessIdentity() throws Exception {
// authorizationService.loadUserByUsername(WebUtils.formatProxyDn(ACCESS_EXCEPTION_IN_IDENTITY_PROVIDER_DN));
// }
//
// /**
// * Ensures that user authorities are properly loaded from the authority
// * provider.
// *
// * @throws Exception
// */
// @Test
// public void testVerificationRequiredUser() throws Exception {
// NiFiUserDetails userDetails = (NiFiUserDetails) authorizationService.loadUserByUsername(WebUtils.formatProxyDn(VERIFICATION_REQUIRED_DN));
// NiFiUser user = userDetails.getNiFiUser();
// Mockito.verify(authorityProvider).getAuthorities(VERIFICATION_REQUIRED_DN);
//
// // ensure the user details
// Assert.assertEquals(VERIFICATION_REQUIRED_DN, user.getDn());
// Assert.assertEquals(1, user.getAuthorities().size());
// Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_MONITOR));
// }
//
// /**
// * Ensures that user authorities are not loaded when the cache is still
// * valid.
// *
// * @throws Exception
// */
// @Test
// public void testVerificationNotRequiredUser() throws Exception {
// NiFiUserDetails userDetails = (NiFiUserDetails) authorizationService.loadUserByUsername(WebUtils.formatProxyDn(VERIFICATION_NOT_REQUIRED_DN));
// NiFiUser user = userDetails.getNiFiUser();
// Mockito.verify(authorityProvider, Mockito.never()).getAuthorities(VERIFICATION_NOT_REQUIRED_DN);
//
// // ensure the user details
// Assert.assertEquals(VERIFICATION_NOT_REQUIRED_DN, user.getDn());
// Assert.assertEquals(2, user.getAuthorities().size());
// Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_ADMIN));
// Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_DFM));
// }
//
// /**
// * Ensures that new users are automatically created when the authority
// * provider has their authorities.
// *
// * @throws Exception
// */
// @Test
// public void testNewUser() throws Exception {
// NiFiUserDetails userDetails = (NiFiUserDetails) authorizationService.loadUserByUsername(WebUtils.formatProxyDn(NEW_USER_DN));
// NiFiUser user = userDetails.getNiFiUser();
// Mockito.verify(authorityProvider).getAuthorities(NEW_USER_DN);
//
// // ensure the user details
// Assert.assertEquals(NEW_USER_DN, user.getDn());
// Assert.assertEquals(1, user.getAuthorities().size());
// Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_MONITOR));
// }
}

View File

@ -22,12 +22,15 @@ import org.apache.nifi.cluster.authorization.protocol.message.ProtocolMessage;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.nifi.authorization.Authority;
import org.apache.nifi.authorization.AuthorityProvider;
import org.apache.nifi.authorization.AuthorityProviderConfigurationContext;
import org.apache.nifi.authorization.AuthorityProviderInitializationContext;
import org.apache.nifi.authorization.DownloadAuthorization;
import org.apache.nifi.authorization.annotation.AuthorityProviderContext;
import org.apache.nifi.authorization.exception.AuthorityAccessException;
import org.apache.nifi.authorization.exception.IdentityAlreadyExistsException;
@ -282,6 +285,11 @@ public class NodeAuthorizationProvider implements AuthorityProvider, Application
throw new AuthorityAccessException("Nodes are not allowed to ungroup.");
}
@Override
public DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes) throws UnknownIdentityException, AuthorityAccessException {
return DownloadAuthorization.approved();
}
@Override
public String getGroupForUser(String dn) throws UnknownIdentityException, AuthorityAccessException {
// create message

View File

@ -23,7 +23,7 @@
<artifactId>file-authorization-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Authorization Provider: File</name>
<name>NiFi File Authorization Provider</name>
<build>
<resources>

View File

@ -21,6 +21,8 @@ import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
@ -492,6 +494,20 @@ public class FileAuthorizationProvider implements AuthorityProvider {
}
}
/**
* Grants access to download content regardless of FlowFile attributes.
*
* @param dnChain
* @param attributes
* @return
* @throws UnknownIdentityException
* @throws AuthorityAccessException
*/
@Override
public DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes) throws UnknownIdentityException, AuthorityAccessException {
return DownloadAuthorization.approved();
}
/**
* Locates the user with the specified DN.
*

View File

@ -160,13 +160,13 @@ public class ProvenanceResource extends ApplicationResource {
*/
@POST
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@PreAuthorize("hasRole('ROLE_PROVENANCE')")
@PreAuthorize("hasRole('ROLE_PROVENANCE') and hasRole('ROLE_DFM')")
@Path("/replays")
@TypeHint(ProvenanceEventEntity.class)
public Response submitReplay(
@Context HttpServletRequest httpServletRequest,
@FormParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
@QueryParam("clusterNodeId") String clusterNodeId,
@FormParam("clusterNodeId") String clusterNodeId,
@FormParam("eventId") LongParameter eventId) {
// ensure the event id is specified
@ -190,7 +190,7 @@ public class ProvenanceResource extends ApplicationResource {
targetNodes.add(targetNode.getNodeId());
// replicate the request to the specific node
return clusterManager.applyRequest(HttpMethod.GET, getAbsolutePath(), getRequestParameters(true), getHeaders(), targetNodes).getResponse();
return clusterManager.applyRequest(HttpMethod.POST, getAbsolutePath(), getRequestParameters(true), getHeaders(), targetNodes).getResponse();
}
}
@ -231,7 +231,7 @@ public class ProvenanceResource extends ApplicationResource {
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Path("/events/{id}/content/input")
@PreAuthorize("hasRole('ROLE_PROVENANCE') and hasRole('ROLE_DFM')")
@PreAuthorize("hasRole('ROLE_PROVENANCE')")
public Response getInputContent(
@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
@QueryParam("clusterNodeId") String clusterNodeId,
@ -305,7 +305,7 @@ public class ProvenanceResource extends ApplicationResource {
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Path("/events/{id}/content/output")
@PreAuthorize("hasRole('ROLE_PROVENANCE') and hasRole('ROLE_DFM')")
@PreAuthorize("hasRole('ROLE_PROVENANCE')")
public Response getOutputContent(
@QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId,
@QueryParam("clusterNodeId") String clusterNodeId,

View File

@ -111,8 +111,11 @@ import org.apache.nifi.web.util.DownloadableContent;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.admin.service.UserService;
import org.apache.nifi.authorization.DownloadAuthorization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
/**
*
@ -124,6 +127,7 @@ public class ControllerFacade implements ControllerServiceProvider {
// nifi components
private FlowController flowController;
private FlowService flowService;
private UserService userService;
// properties
private NiFiProperties properties;
@ -787,6 +791,28 @@ public class ControllerFacade implements ControllerServiceProvider {
throw new ResourceNotFoundException("Unable to find the specified event.");
}
// get the flowfile attributes
final Map<String, String> attributes = event.getAttributes();
// calculate the dn chain
final List<String> dnChain = new ArrayList<>();
// build the dn chain
NiFiUser chainedUser = user;
do {
// add the entry for this user
dnChain.add(chainedUser.getDn());
// go to the next user in the chain
chainedUser = chainedUser.getChain();
} while (chainedUser != null);
// ensure the users in this chain are allowed to download this content
final DownloadAuthorization downloadAuthorization = userService.authorizeDownload(dnChain, attributes);
if (!downloadAuthorization.isApproved()) {
throw new AccessDeniedException(downloadAuthorization.getExplanation());
}
// get the filename and fall back to the idnetifier (should never happen)
String filename = event.getAttributes().get(CoreAttributes.FILENAME.key());
if (filename == null) {
@ -1329,6 +1355,10 @@ public class ControllerFacade implements ControllerServiceProvider {
this.properties = properties;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public void setFlowService(FlowService flowService) {
this.flowService = flowService;
}

View File

@ -80,6 +80,7 @@
<property name="properties" ref="nifiProperties"/>
<property name="flowController" ref="flowController"/>
<property name="flowService" ref="flowService"/>
<property name="userService" ref="userService"/>
<property name="dtoFactory" ref="dtoFactory"/>
</bean>
<bean id="serviceFacade" class="org.apache.nifi.web.StandardNiFiServiceFacade">

View File

@ -19,6 +19,7 @@ package org.apache.nifi.integration.util;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.nifi.authorization.Authority;
@ -29,6 +30,7 @@ import org.apache.nifi.authorization.exception.AuthorityAccessException;
import org.apache.nifi.authorization.exception.ProviderCreationException;
import org.apache.nifi.authorization.exception.UnknownIdentityException;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.DownloadAuthorization;
/**
*
@ -169,4 +171,9 @@ public class NiFiTestAuthorizationProvider implements AuthorityProvider {
public void ungroup(String group) throws UnknownIdentityException, AuthorityAccessException {
}
@Override
public DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes) throws UnknownIdentityException, AuthorityAccessException {
return DownloadAuthorization.approved();
}
}

View File

@ -84,17 +84,18 @@ nf.ProvenanceTable = (function () {
var downloadContent = function (direction) {
var eventId = $('#provenance-event-id').text();
// build the parameters
var parameters = {};
// build the url
var url = config.urls.provenance + '/events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction);
// conditionally include the cluster node id
var clusterNodeId = $('#provenance-event-cluster-node-id').text();
if (!nf.Common.isBlank(clusterNodeId)) {
parameters['clusterNodeId'] = clusterNodeId;
window.open(url + '?' + $.param({
'clusterNodeId': clusterNodeId
}));
} else {
window.open(url);
}
// get the content
nf.Common.submit('GET', config.urls.provenance + '/events/' + encodeURIComponent(eventId) + '/content/' + encodeURIComponent(direction), parameters);
};
/**
@ -191,6 +192,29 @@ nf.ProvenanceTable = (function () {
}
});
// input download
$('#input-content-download').on('click', function () {
downloadContent('input');
});
// output download
$('#output-content-download').on('click', function () {
downloadContent('output');
});
// if a content viewer url is specified, use it
if (isContentViewConfigured()) {
// input view
$('#input-content-view').on('click', function () {
viewContent('input');
});
// output view
$('#output-content-view').on('click', function () {
viewContent('output');
});
}
// handle the replay and downloading
if (nf.Common.isDFM()) {
// replay
@ -220,29 +244,6 @@ nf.ProvenanceTable = (function () {
$('#event-details-dialog').modal('hide');
});
// input download
$('#input-content-download').on('click', function () {
downloadContent('input');
});
// output download
$('#output-content-download').on('click', function () {
downloadContent('output');
});
// if a content viewer url is specified, use it
if (isContentViewConfigured()) {
// input view
$('#input-content-view').on('click', function () {
viewContent('input');
});
// output view
$('#output-content-view').on('click', function () {
viewContent('output');
});
}
// show the replay panel
$('#replay-details').show();
}
@ -1281,19 +1282,17 @@ nf.ProvenanceTable = (function () {
$('#output-content-download').hide();
if (nf.Common.isDFM()) {
if (event.inputContentAvailable === true) {
$('#input-content-download').show();
if (event.inputContentAvailable === true) {
$('#input-content-download').show();
if (isContentViewConfigured()) {
$('#input-content-view').show();
} else {
$('#input-content-view').hide();
}
if (isContentViewConfigured()) {
$('#input-content-view').show();
} else {
$('#input-content-download').hide();
$('#input-content-view').hide();
}
} else {
$('#input-content-download').hide();
$('#input-content-view').hide();
}
} else {
$('#output-content-details').show();
@ -1327,32 +1326,30 @@ nf.ProvenanceTable = (function () {
outputContentSize.attr('title', nf.Common.formatInteger(event.outputContentClaimFileSizeBytes) + ' bytes');
}
if (nf.Common.isDFM()) {
if (event.inputContentAvailable === true) {
$('#input-content-download').show();
if (event.inputContentAvailable === true) {
$('#input-content-download').show();
if (isContentViewConfigured()) {
$('#input-content-view').show();
} else {
$('#input-content-view').hide();
}
if (isContentViewConfigured()) {
$('#input-content-view').show();
} else {
$('#input-content-download').hide();
$('#input-content-view').hide();
}
} else {
$('#input-content-download').hide();
$('#input-content-view').hide();
}
if (event.outputContentAvailable === true) {
$('#output-content-download').show();
if (event.outputContentAvailable === true) {
$('#output-content-download').show();
if (isContentViewConfigured()) {
$('#output-content-view').show();
} else {
$('#output-content-view').hide();
}
if (isContentViewConfigured()) {
$('#output-content-view').show();
} else {
$('#output-content-download').hide();
$('#output-content-view').hide();
}
} else {
$('#output-content-download').hide();
$('#output-content-view').hide();
}
}

View File

@ -270,7 +270,7 @@ nf.TemplatesTable = (function () {
if (nf.Common.isDefinedAndNotNull(grid)) {
var data = grid.getData();
var item = data.getItem(row);
nf.Common.submit('GET', config.urls.templates + '/' + encodeURIComponent(item.id));
window.open(config.urls.templates + '/' + encodeURIComponent(item.id));
}
},

View File

@ -51,10 +51,12 @@ public class NiFiAuthorizationService implements UserDetailsService {
private NiFiProperties properties;
/**
* Loads the user details for the specified dn. Method must be synchronized
* since multiple requests from the same user may be sent simultaneously.
* Since we don't want to run the account verification process multiple for
* the same user, we treat each request atomically.
* Loads the user details for the specified dn.
*
* Synchronizing because we want each request to be authorized atomically since
* each may contain any number of DNs. We wanted an access decision made
* for each individual request as a whole (without other request potentially
* impacting it).
*
* @param rawProxyChain
* @return
@ -72,6 +74,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<String> dnIter = dnList.iterator(); dnIter.hasNext();) {
final String dn = dnIter.next();
@ -88,6 +92,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()) {
@ -101,6 +113,12 @@ public class NiFiAuthorizationService implements UserDetailsService {
throw new UsernameNotFoundException(String.format("An account request was generated for the proxy '%s'.", dn));
} catch (AdministrationException ae) {
throw new AuthenticationServiceException(String.format("Unable to create an account request for '%s': %s", dn, ae.getMessage()), ae);
} catch (IllegalArgumentException iae) {
// check then modified... account didn't exist when getting the user details but did when
// attempting to auto create the user account request
final String message = String.format("Account request was already submitted for '%s'", dn);
logger.warn(message);
throw new AccountStatusException(message) {};
}
} else {
logger.warn(String.format("Untrusted proxy '%s' must be authorized with '%s' authority: %s", dn, Authority.ROLE_PROXY.toString(), unfe.getMessage()));
@ -112,6 +130,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);
}
}
}
@ -131,8 +155,7 @@ public class NiFiAuthorizationService implements UserDetailsService {
} catch (AdministrationException ase) {
throw new AuthenticationServiceException(String.format("An error occurred while accessing the user credentials for '%s': %s", dn, ase.getMessage()), ase);
} catch (AccountDisabledException | AccountPendingException e) {
throw new AccountStatusException(e.getMessage(), e) {
};
throw new AccountStatusException(e.getMessage(), e) {};
} catch (AccountNotFoundException anfe) {
throw new UsernameNotFoundException(anfe.getMessage());
}

View File

@ -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());
}
}

View File

@ -16,6 +16,8 @@
*/
package org.apache.nifi.authorization;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.nifi.authorization.exception.AuthorityAccessException;
import org.apache.nifi.authorization.exception.IdentityAlreadyExistsException;
@ -136,6 +138,23 @@ public interface AuthorityProvider {
*/
void ungroup(String group) throws AuthorityAccessException;
/**
* Determines whether the user in the specified dnChain should be able to
* download the content for the flowfile with the specified attributes.
*
* The first dn in the chain is the end user that the request was issued on
* behalf of. The subsequent dn's in the chain represent entities proxying
* the user's request with the last being the proxy that sent the current
* request.
*
* @param dnChain
* @param attributes
* @return
* @throws UnknownIdentityException
* @throws AuthorityAccessException
*/
DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes) throws UnknownIdentityException, AuthorityAccessException;
/**
* Called immediately after instance creation for implementers to perform
* additional setup

View File

@ -0,0 +1,86 @@
/*
* 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.authorization;
/**
* Represents a decision whether authorization is granted to download content.
*/
public class DownloadAuthorization {
private static enum Result {
Approved,
Denied;
};
private static final DownloadAuthorization APPROVED = new DownloadAuthorization(Result.Approved, null);
private final Result result;
private final String explanation;
/**
* Creates a new DownloadAuthorization with the specified result and explanation.
*
* @param result
* @param explanation
*/
private DownloadAuthorization(Result result, String explanation) {
if (Result.Denied.equals(result) && explanation == null) {
throw new IllegalArgumentException("An explanation is required when the download request is denied.");
}
this.result = result;
this.explanation = explanation;
}
/**
* Whether or not the download request is approved.
*
* @return
*/
public boolean isApproved() {
return Result.Approved.equals(result);
}
/**
* If the download request is denied, the reason why. Null otherwise.
*
* @return
*/
public String getExplanation() {
return explanation;
}
/**
* Creates a new approved DownloadAuthorization.
*
* @return
*/
public static DownloadAuthorization approved() {
return APPROVED;
}
/**
* Creates a new denied DownloadAuthorization with the specified explanation.
*
* @param explanation
* @return
* @throws IllegalArgumentException if explanation is null
*/
public static DownloadAuthorization denied(String explanation) {
return new DownloadAuthorization(Result.Denied, explanation);
}
}