From aaf14c45c96077c0075af8f3442e392b717244c1 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Wed, 7 Oct 2015 13:33:34 -0400 Subject: [PATCH] NIFI-655: - Refactoring web security to use Spring Security Java Configuration. - Introducing security in Web UI in order to get JWT. NIFI-655: - Setting up the resources (js/css) for the login page. NIFI-655: - Adding support for configuring anonymous roles. - Addressing checkstyle violations. NIFI-655: - Moving to token api to web-api. - Creating an LoginProvider API for user/pass based authentication. - Creating a module for funneling access to the authorized useres. NIFI-655: - Moving away from usage of DN to identity throughout the application (from the user db to the authorization provider). - Updating the authorized users schema to support login users. - Creating an extension point for authentication of users based on username/password. NIFI-655: - Creating an endpoint for returning the identity of the current user. - Updating the LoginAuthenticationFilter. NIFI-655: - Moving NiFi registration to the login page. - Running the authentication filters in a different order to ensure we can disambiguate each case. - Starting to layout each case... Forbidden, Login, Create User, Create NiFi Account. NIFI-655: - Addressing checkstyle issues. NIFI-655: - Making nf-storage available in the login page. - Requiring use of local storage. - Ignoring security for GET requests when obtaining the login configuration. NIFI-655: - Adding a new endpoint to obtain the status of a user registration. - Updated the login page loading to ensure all possible states work. NIFI-655: - Ensuring we know the necessary state before we attempt to render the login page. - Building the proxy chain in the JWT authentication filter. - Only rendering the login when appropriate. NIFI-655: - Starting to style the login page. - Added simple 'login' support by identifying username/password. Issuing JWT token coming... - Added logout support - Rendering the username when appropriate. NIFI-655: - Extracting certificate validation into a utility class. - Fixing checkstyle issues. - Cleaning up the web security context. - Removing proxy chain checking where possible. NIFI-655: - Starting to add support for registration. - Creating registration form. NIFI-655: - Starting to implement the JWT service. - Parsing JWT on client side in order to render who the user currently is when logged in. NIFI-655: - Allowing the user to link back to the log in page from the new account page. - Renaming DN to identity where possible. NIFI-655: - Fixing checkstyle issues. NIFI-655: - Adding more/better support for logging out. NIFI-655: - Fixing checkstyle issues. NIFI-655: - Adding a few new exceptions for the login identity provider. NIFI-655: - Disabling log in by default initially. - Restoring authorization service unit test. NIFI-655: - Fixing checkstyle issues. NIFI-655: - Updating packages for log in filters. - Handling new registration exceptions. - Code clean up. NIFI-655: - Removing registration support. - Removing file based implementation. NIFI-655: - Removing file based implementation. NIFI-655: - Removing unused spring configuration files. NIFI-655: - Making the auto wiring more explicit. NIFI-655: - Removing unused dependencies. NIFI-655: - Removing unused filter. NIFI-655: - Updating the login API authenticate method to use a richer set of exceptions. - UI code clean. NIFI-655: - Ensuring the login identity provider is able to switch context classloaders via the standard NAR mechanisms. NIFI-655: - Initial commit of the LDAP based identity providers. - Fixed issue when attempting to log into a NiFi that does not support new account requests. NIFI-655: - Allowing the ldap provider to specify if client authentication is required/desired. NIFI-655: - Persisting keys to sign user tokens. - Allowing the identity provider to specify the token expiration. - Code clean up. NIFI-655: - Ensuring identities are unique in the key table. NIFI-655: - Adding support for specifying the user search base and user search filter in the active directory provider. NIFI-655: - Fixing checkstyle issues. NIFI-655: - Adding automatic client side token renewal. NIFI-655: - Ensuring the logout link is rendered when appropriate. NIFI-655: - Adding configuration options for referrals and connect/read timeouts NIFI-655: - Added an endpoint for access details including configuration, creating tokens, and checking status. - Updated DTOs and client side to utilize new endpoints. NIFI-655: - Refactoring certificate extraction and validation. - Refactoring how expiration is specified in the login identity providers. - Adding unit tests for the access endpoints. - Code clean up. NIFI-655: - Keeping token expiration between 1 minute and 12 hours. NIFI-655: - Using the user identity provided by the login identity provider. NIFI-655: - Fixed typo in error message for unrecognized authentication strategy. Signed-off-by: Matt Gilman NIFI-655. - Added logback-test.xml configuration resource for nifi-web-security. Signed-off-by: Matt Gilman NIFI-655. - Added issuer field to LoginAuthenticationToken. - Updated AccessResource to pass identity provider class name when creating LoginAuthenticationTokens. - Began refactoring JWT logic from request parsing logic in JwtService. - Added unit tests for JWT logic. Signed-off-by: Matt Gilman NIFI-655. - Changed issuer field to use FQ class name because some classes return an empty string for getSimpleName(). - Finished refactoring JWT logic from request parsing logic in JwtService. - Updated AccessResource and JwtAuthenticationFilter to call new JwtService methods decoupled from request header parsing. - Added extensive unit tests for JWT logic. Signed-off-by: Matt Gilman NIFI-655: - Refactoring key service to expose the key id. - Handling client side expiration better. - Removing specialized active directory provider and abstract ldap provider. NIFI-655. - Updated JwtService and JwtServiceTest to use Key POJO instead of raw String key from KeyService. Signed-off-by: Matt Gilman NIFI-655: - Fixing typo when loading the ldap connect timeout. - Providing a better experience for session expiration. - Using ellipsis for lengthly user name. - Adding an issuer to the authentication response so the LIP can specify the appropriate value. NIFI-655: - Showing a logging in notification during the log in process. NIFI-655: - Removing unnecessary class. NIFI-655: - Fixing checkstyle issues. - Showing the progress spinner while submitting account justification. NIFI-655: - Removing deprecated authentication strategy. - Renaming TLS to START_TLS. - Allowing the protocol to be configured. NIFI-655: - Fixing issue detecting the presence of DN column NIFI-655: - Pre-populating the login-identity-providers.xml file with necessary properties and documentation. - Renaming the Authentication Duration property name. NIFI-655: - Updating documentation for the failure response codes. NIFI-655: - Ensuring the user identity is not too long. NIFI-655: - Updating default authentication expiration to 12 hours. NIFI-655: - Remaining on the login form when there is any unsuccessful login attempt. - Fixing checkstyle issues. --- LICENSE | 22 + .../AuthenticationResponse.java | 65 + .../nifi/authentication/LoginCredentials.java | 39 + .../authentication/LoginIdentityProvider.java | 61 + ...nIdentityProviderConfigurationContext.java | 48 + ...IdentityProviderInitializationContext.java | 27 + .../LoginIdentityProviderLookup.java | 25 + .../LoginIdentityProviderContext.java | 35 + .../exception/IdentityAccessException.java | 33 + .../InvalidLoginCredentialsException.java | 33 + .../nifi/authorization/AuthorityProvider.java | 42 +- .../nifi/web/NiFiWebConfigurationContext.java | 2 +- .../org/apache/nifi/web/NiFiWebContext.java | 2 +- nifi-assembly/LICENSE | 22 + nifi-assembly/pom.xml | 8 + .../org/apache/nifi/util/NiFiProperties.java | 64 +- .../nifi/security/util/CertificateUtils.java | 49 +- .../nifi-administration/pom.xml | 1 - .../admin/AuditDataSourceFactoryBean.java | 23 +- .../nifi/admin/KeyDataSourceFactoryBean.java | 154 ++ .../nifi/admin/UserDataSourceFactoryBean.java | 93 +- .../org/apache/nifi/admin/dao/DAOFactory.java | 2 + .../org/apache/nifi/admin/dao/KeyDAO.java | 49 + .../nifi/admin/dao/impl/DAOFactoryImpl.java | 6 + .../admin/dao/impl/StandardActionDAO.java | 10 +- .../nifi/admin/dao/impl/StandardKeyDAO.java | 154 ++ .../nifi/admin/dao/impl/StandardUserDAO.java | 53 +- .../apache/nifi/admin/service/KeyService.java | 42 + .../service/action/AbstractUserAction.java | 4 +- .../service/action/AuthorizeUserAction.java | 24 +- .../service/action/DisableUserAction.java | 6 +- .../service/action/GetKeyByIdAction.java | 42 + .../action/GetKeyByIdentityAction.java | 42 + .../service/action/GetOrCreateKeyAction.java | 48 + .../action/RequestUserAccountAction.java | 14 +- .../action/SeedUserAccountsAction.java | 18 +- .../service/action/UngroupUserAction.java | 6 +- .../service/action/UpdateUserAction.java | 24 +- .../service/action/UpdateUserGroupAction.java | 32 +- .../service/impl/StandardKeyService.java | 126 ++ .../main/java/org/apache/nifi/key/Key.java | 69 + .../java/org/apache/nifi/user/NiFiUser.java | 18 +- .../resources/nifi-administration-context.xml | 16 + .../action/AuthorizeUserActionTest.java | 100 +- .../service/action/CreateUserActionTest.java | 12 +- .../service/action/DisableUserActionTest.java | 14 +- .../action/RequestUserAccountActionTest.java | 22 +- .../action/SeedUserAccountsActionTest.java | 44 +- .../action/SetUserAuthoritiesActionTest.java | 18 +- .../web/api/dto/AccessConfigurationDTO.java | 61 + .../nifi/web/api/dto/AccessStatusDTO.java | 101 + .../api/entity/AccessConfigurationEntity.java | 43 + .../web/api/entity/AccessStatusEntity.java | 43 + .../nifi/web/api/entity/IdentityEntity.java | 52 + .../org/apache/nifi/nar/ExtensionManager.java | 17 +- .../nifi/nar/NarThreadContextClassLoader.java | 2 + .../conf/login-identity-providers.xml | 92 + .../src/main/resources/conf/nifi.properties | 3 + .../apache/nifi/web/server/JettyServer.java | 8 +- .../apache/nifi/audit/ControllerAuditor.java | 8 +- .../nifi/audit/ControllerServiceAuditor.java | 12 +- .../org/apache/nifi/audit/FunnelAuditor.java | 2 +- .../org/apache/nifi/audit/PortAuditor.java | 6 +- .../nifi/audit/ProcessGroupAuditor.java | 6 +- .../apache/nifi/audit/ProcessorAuditor.java | 6 +- .../nifi/audit/RelationshipAuditor.java | 4 +- .../nifi/audit/RemoteProcessGroupAuditor.java | 6 +- .../nifi/audit/ReportingTaskAuditor.java | 6 +- .../org/apache/nifi/audit/SnippetAuditor.java | 2 +- .../apache/nifi/web/NiFiServiceFacade.java | 7 + .../nifi/web/NiFiWebApiConfiguration.java | 40 + .../web/NiFiWebApiSecurityConfiguration.java | 179 ++ .../nifi/web/StandardNiFiServiceFacade.java | 28 +- .../StandardNiFiWebConfigurationContext.java | 8 +- .../nifi/web/StandardNiFiWebContext.java | 8 +- .../apache/nifi/web/api/AccessResource.java | 424 ++++ .../nifi/web/api/ApplicationResource.java | 16 +- .../nifi/web/api/ControllerResource.java | 65 +- .../org/apache/nifi/web/api/UserResource.java | 49 +- .../config/AccessDeniedExceptionMapper.java | 2 +- .../InvalidAuthenticationExceptionMapper.java | 44 + .../apache/nifi/web/api/dto/DtoFactory.java | 2 +- .../nifi/web/controller/ControllerFacade.java | 17 +- .../web/dao/impl/StandardConnectionDAO.java | 2 +- .../apache/nifi/web/filter/RequestLogger.java | 6 +- .../main/resources/nifi-web-api-context.xml | 9 + .../src/main/webapp/WEB-INF/web.xml | 13 +- .../AccessTokenEndpointTest.java | 292 +++ .../util/NiFiTestAuthorizationProvider.java | 3 +- .../util/NiFiTestLoginIdentityProvider.java | 75 + .../nifi/integration/util/NiFiTestServer.java | 9 +- .../nifi/integration/util/NiFiTestUser.java | 232 +- ....nifi.authentication.LoginIdentityProvider | 15 + .../login-identity-providers.xml | 24 + .../resources/access-control/nifi.properties | 8 +- .../nifi-web/nifi-web-security/pom.xml | 43 + .../org/apache/nifi/web/security/DnUtils.java | 85 - .../InvalidAuthenticationException.java | 35 + .../NiFiAuthenticationEntryPoint.java | 44 +- .../security/NiFiAuthenticationFilter.java | 209 ++ .../security/NiFiAuthenticationProvider.java | 73 + .../web/security/ProxiedEntitiesUtils.java | 147 ++ .../anonymous/NiFiAnonymousUserFilter.java | 56 +- .../NiFiAuthorizationService.java | 46 +- .../security/jwt/JwtAuthenticationFilter.java | 80 + .../nifi/web/security/jwt/JwtService.java | 162 ++ .../NodeAuthorizedUserFilter.java | 63 +- .../LoginIdentityProviderFactoryBean.java | 312 +++ ...nIdentityProviderConfigurationContext.java | 51 + ...IdentityProviderInitializationContext.java | 45 + .../token/LoginAuthenticationToken.java | 123 + .../NewAccountAuthenticationRequestToken.java | 40 + .../token/NewAccountAuthenticationToken.java | 46 + .../token/NiFiAuthenticationRequestToken.java | 54 + .../token/NiFiAuthorizationToken.java | 50 + .../web/security/user/NewAccountRequest.java | 47 + .../web/security/user/NiFiUserDetails.java | 3 +- .../nifi/web/security/user/NiFiUserUtils.java | 27 +- .../x509/X509AuthenticationFilter.java | 308 +-- .../x509/X509CertificateExtractor.java | 4 +- .../x509/X509CertificateValidator.java | 58 + .../security/x509/X509IdentityProvider.java | 94 + .../x509/ocsp/OcspCertificateValidator.java | 20 +- .../resources/nifi-web-security-context.xml | 85 +- .../src/main/xsd/login-identity-providers.xsd | 49 + .../NiFiAuthorizationServiceTest.java | 64 +- .../nifi/web/security/jwt/JwtServiceTest.java | 445 ++++ .../src/test/resources/logback-test.xml | 36 + .../nifi-web/nifi-web-ui/pom.xml | 53 +- .../src/main/resources/META-INF/LICENSE | 22 + .../filters/bulletin-board.properties | 1 + .../main/resources/filters/canvas.properties | 3 +- .../main/resources/filters/cluster.properties | 1 + .../resources/filters/counters.properties | 1 + .../main/resources/filters/history.properties | 1 + .../resources/filters/login-min.properties | 18 + .../main/resources/filters/login.properties | 25 + .../resources/filters/provenance.properties | 1 + .../main/resources/filters/summary.properties | 1 + .../resources/filters/templates.properties | 1 + .../main/resources/filters/users.properties | 1 + .../webapp/WEB-INF/pages/bulletin-board.jsp | 1 + .../src/main/webapp/WEB-INF/pages/canvas.jsp | 3 +- .../src/main/webapp/WEB-INF/pages/cluster.jsp | 1 + .../main/webapp/WEB-INF/pages/counters.jsp | 1 + .../src/main/webapp/WEB-INF/pages/history.jsp | 1 + .../src/main/webapp/WEB-INF/pages/login.jsp | 54 + .../webapp/WEB-INF/pages/message-page.jsp | 4 +- .../main/webapp/WEB-INF/pages/provenance.jsp | 1 + .../src/main/webapp/WEB-INF/pages/summary.jsp | 1 + .../main/webapp/WEB-INF/pages/templates.jsp | 1 + .../src/main/webapp/WEB-INF/pages/users.jsp | 1 + .../WEB-INF/partials/canvas/canvas-header.jsp | 11 + .../WEB-INF/partials/canvas/registration.jsp | 44 - .../WEB-INF/partials/login/login-form.jsp | 32 + .../WEB-INF/partials/login/login-message.jsp} | 17 +- .../WEB-INF/partials/login/login-progress.jsp | 22 + .../partials/login/login-submission.jsp} | 15 +- .../partials/login/nifi-registration-form.jsp | 38 + .../webapp/WEB-INF/partials/message-pane.jsp | 7 +- .../partials/users/user-details-dialog.jsp | 2 +- .../src/main/webapp/WEB-INF/web.xml | 22 +- .../src/main/webapp/css/canvas.css | 1 - .../src/main/webapp/css/header.css | 18 + .../nifi-web-ui/src/main/webapp/css/login.css | 95 + .../nifi-web-ui/src/main/webapp/css/main.css | 13 +- .../main/webapp/js/jquery/jquery.base64.js | 123 + .../js/nf/bulletin-board/nf-bulletin-board.js | 2 + .../webapp/js/nf/canvas/nf-canvas-header.js | 49 +- .../src/main/webapp/js/nf/canvas/nf-canvas.js | 232 +- .../webapp/js/nf/canvas/nf-registration.js | 71 - .../main/webapp/js/nf/canvas/nf-storage.js | 139 -- .../main/webapp/js/nf/cluster/nf-cluster.js | 2 + .../main/webapp/js/nf/counters/nf-counters.js | 2 + .../main/webapp/js/nf/history/nf-history.js | 2 + .../src/main/webapp/js/nf/login/nf-login.js | 302 +++ .../src/main/webapp/js/nf/nf-common.js | 1982 +++++++++-------- .../src/main/webapp/js/nf/nf-dialog.js | 23 +- .../src/main/webapp/js/nf/nf-storage.js | 172 ++ .../webapp/js/nf/provenance/nf-provenance.js | 2 + .../main/webapp/js/nf/summary/nf-summary.js | 2 + .../webapp/js/nf/templates/nf-templates.js | 2 + .../src/main/webapp/js/nf/users/nf-users.js | 2 + .../nifi-ldap-iaa-providers-nar/pom.xml | 32 + .../nifi-ldap-iaa-providers/pom.xml | 60 + .../nifi/ldap/LdapAuthenticationStrategy.java | 27 + .../org/apache/nifi/ldap/LdapProvider.java | 279 +++ .../apache/nifi/ldap/ReferralStrategy.java} | 48 +- ....nifi.authentication.LoginIdentityProvider | 15 + .../nifi-ldap-iaa-providers-bundle/pom.xml | 38 + nifi-nar-bundles/pom.xml | 1 + pom.xml | 67 +- 192 files changed, 8701 insertions(+), 2434 deletions(-) create mode 100644 nifi-api/src/main/java/org/apache/nifi/authentication/AuthenticationResponse.java create mode 100644 nifi-api/src/main/java/org/apache/nifi/authentication/LoginCredentials.java create mode 100644 nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProvider.java create mode 100644 nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderConfigurationContext.java create mode 100644 nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderInitializationContext.java create mode 100644 nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderLookup.java create mode 100644 nifi-api/src/main/java/org/apache/nifi/authentication/annotation/LoginIdentityProviderContext.java create mode 100644 nifi-api/src/main/java/org/apache/nifi/authentication/exception/IdentityAccessException.java create mode 100644 nifi-api/src/main/java/org/apache/nifi/authentication/exception/InvalidLoginCredentialsException.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/KeyDataSourceFactoryBean.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessConfigurationDTO.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessStatusDTO.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessConfigurationEntity.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessStatusEntity.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/IdentityEntity.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/login-identity-providers.xml create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiConfiguration.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/InvalidAuthenticationExceptionMapper.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml delete mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java rename nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/{authentication => }/NiFiAuthenticationEntryPoint.java (58%) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java rename nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/{authorization => node}/NodeAuthorizedUserFilter.java (65%) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/StandardLoginIdentityProviderConfigurationContext.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/StandardLoginIdentityProviderInitializationContext.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationRequestToken.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationToken.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthenticationRequestToken.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationToken.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NewAccountRequest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509CertificateValidator.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509IdentityProvider.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/xsd/login-identity-providers.xsd create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/resources/logback-test.xml create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login-min.properties create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login.properties create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp delete mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/registration.jsp create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-form.jsp rename nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/{nifi-web-api/src/test/resources/access-control/controller-services.xml => nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-message.jsp} (71%) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-progress.jsp rename nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/{nifi-web-api/src/test/resources/access-control/reporting-tasks.xml => nifi-web-ui/src/main/webapp/WEB-INF/partials/login/login-submission.jsp} (70%) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/login/nifi-registration-form.jsp create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js delete mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-registration.js delete mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-storage.js create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js create mode 100644 nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers-nar/pom.xml create mode 100644 nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/pom.xml create mode 100644 nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapAuthenticationStrategy.java create mode 100644 nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java rename nifi-nar-bundles/{nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/registration.css => nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ReferralStrategy.java} (66%) create mode 100644 nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider create mode 100644 nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/pom.xml diff --git a/LICENSE b/LICENSE index 59741e6035..f4be753c80 100644 --- a/LICENSE +++ b/LICENSE @@ -374,6 +374,28 @@ For details see http://jqueryui.com OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +This product bundles 'jquery.base64.js' which is available under an MIT style license. + + Copyright (c) 2013 Yannick Albert (http://yckart.com/) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + This product bundles 'SlickGrid v2.2' which is available under an MIT style license. Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/AuthenticationResponse.java b/nifi-api/src/main/java/org/apache/nifi/authentication/AuthenticationResponse.java new file mode 100644 index 0000000000..e9999fcc56 --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/authentication/AuthenticationResponse.java @@ -0,0 +1,65 @@ +/* + * 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.authentication; + +/** + * Authentication response for a user login attempt. + */ +public class AuthenticationResponse { + + private final String identity; + private final String username; + private final long expiration; + private final String issuer; + + /** + * Creates an authentication response. The username and how long the authentication is valid in milliseconds + * + * @param identity The user identity + * @param username The username + * @param expiration The expiration in milliseconds + * @param issuer The issuer of the token + */ + public AuthenticationResponse(final String identity, final String username, final long expiration, final String issuer) { + this.identity = identity; + this.username = username; + this.expiration = expiration; + this.issuer = issuer; + } + + public String getIdentity() { + return identity; + } + + public String getUsername() { + return username; + } + + public String getIssuer() { + return issuer; + } + + /** + * Returns the expiration of a given authentication in milliseconds. + * + * @return The expiration in milliseconds + */ + public long getExpiration() { + return expiration; + } + +} diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/LoginCredentials.java b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginCredentials.java new file mode 100644 index 0000000000..afb7827136 --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginCredentials.java @@ -0,0 +1,39 @@ +/* + * 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.authentication; + +/** + * Login credentials for a user. + */ +public class LoginCredentials { + + private final String username; + private final String password; + + public LoginCredentials(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProvider.java b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProvider.java new file mode 100644 index 0000000000..54becb37cb --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProvider.java @@ -0,0 +1,61 @@ +/* + * 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.authentication; + +import org.apache.nifi.authentication.exception.IdentityAccessException; +import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException; +import org.apache.nifi.authorization.exception.ProviderCreationException; +import org.apache.nifi.authorization.exception.ProviderDestructionException; + +/** + * Identity provider that is able to authentication a user with username/password credentials. + */ +public interface LoginIdentityProvider { + + /** + * Authenticates the specified login credentials. + * + * @param credentials the credentials + * @return The authentication response + * @throws InvalidLoginCredentialsException The login credentials were invalid + * @throws IdentityAccessException Unable to register the user due to an issue accessing the underlying storage + */ + AuthenticationResponse authenticate(LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException; + + /** + * Called immediately after instance creation for implementers to perform additional setup + * + * @param initializationContext in which to initialize + * @throws ProviderCreationException Unable to initialize + */ + void initialize(LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException; + + /** + * Called to configure the AuthorityProvider. + * + * @param configurationContext at the time of configuration + * @throws ProviderCreationException for any issues configuring the provider + */ + void onConfigured(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException; + + /** + * Called immediately before instance destruction for implementers to release resources. + * + * @throws ProviderDestructionException If pre-destruction fails. + */ + void preDestruction() throws ProviderDestructionException; +} diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderConfigurationContext.java b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderConfigurationContext.java new file mode 100644 index 0000000000..6fe61fc47a --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderConfigurationContext.java @@ -0,0 +1,48 @@ +/* + * 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.authentication; + +import java.util.Map; + +/** + * + */ +public interface LoginIdentityProviderConfigurationContext { + + /** + * @return identifier for the authority provider + */ + String getIdentifier(); + + /** + * Retrieves all properties the component currently understands regardless + * of whether a value has been set for them or not. If no value is present + * then its value is null and thus any registered default for the property + * descriptor applies. + * + * @return Map of all properties + */ + Map getProperties(); + + /** + * @param property to lookup the descriptor and value of + * @return the value the component currently understands for the given + * PropertyDescriptor. This method does not substitute default + * PropertyDescriptor values, so the value returned will be null if not set + */ + String getProperty(String property); +} diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderInitializationContext.java b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderInitializationContext.java new file mode 100644 index 0000000000..7aa72a7860 --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderInitializationContext.java @@ -0,0 +1,27 @@ +/* + * 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.authentication; + +/** + * + */ +public interface LoginIdentityProviderInitializationContext { + + public String getIdentifier(); + + public LoginIdentityProviderLookup getAuthorityProviderLookup(); +} diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderLookup.java b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderLookup.java new file mode 100644 index 0000000000..3cd0ba7d6a --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderLookup.java @@ -0,0 +1,25 @@ +/* + * 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.authentication; + +/** + * + */ +public interface LoginIdentityProviderLookup { + + LoginIdentityProvider getLoginIdentityProvider(String identifier); +} diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/annotation/LoginIdentityProviderContext.java b/nifi-api/src/main/java/org/apache/nifi/authentication/annotation/LoginIdentityProviderContext.java new file mode 100644 index 0000000000..88df25929e --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/authentication/annotation/LoginIdentityProviderContext.java @@ -0,0 +1,35 @@ +/* + * 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.authentication.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * + */ +@Documented +@Target({ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface LoginIdentityProviderContext { +} diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/exception/IdentityAccessException.java b/nifi-api/src/main/java/org/apache/nifi/authentication/exception/IdentityAccessException.java new file mode 100644 index 0000000000..b68c67547b --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/authentication/exception/IdentityAccessException.java @@ -0,0 +1,33 @@ +/* + * 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.authentication.exception; + +/** + * Represents the case when the identity could not be confirmed because it was unable + * to access the backing store. + */ +public class IdentityAccessException extends RuntimeException { + + public IdentityAccessException(String message, Throwable cause) { + super(message, cause); + } + + public IdentityAccessException(String message) { + super(message); + } + +} diff --git a/nifi-api/src/main/java/org/apache/nifi/authentication/exception/InvalidLoginCredentialsException.java b/nifi-api/src/main/java/org/apache/nifi/authentication/exception/InvalidLoginCredentialsException.java new file mode 100644 index 0000000000..fbfa3248e7 --- /dev/null +++ b/nifi-api/src/main/java/org/apache/nifi/authentication/exception/InvalidLoginCredentialsException.java @@ -0,0 +1,33 @@ +/* + * 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.authentication.exception; + +/** + * Represents the case when the identity could not be confirmed because the + * login credentials were invalid. + */ +public class InvalidLoginCredentialsException extends RuntimeException { + + public InvalidLoginCredentialsException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidLoginCredentialsException(String message) { + super(message); + } + +} diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorityProvider.java b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorityProvider.java index 723ec33085..716216d127 100644 --- a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorityProvider.java +++ b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorityProvider.java @@ -31,33 +31,33 @@ import org.apache.nifi.authorization.exception.UnknownIdentityException; public interface AuthorityProvider { /** - * @param dn of the user - * @return whether the user with the specified DN is known to this authority + * @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user. + * @return whether the user with the specified identity is known to this authority * provider. It is not necessary for the user to have any authorities */ - boolean doesDnExist(String dn) throws AuthorityAccessException; + boolean doesDnExist(String identity) throws AuthorityAccessException; /** * Get the authorities for the specified user. If the specified user exists * but does not have any authorities, an empty set should be returned. * - * @param dn of the user to lookup + * @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user. * @return the authorities for the specified user. If the specified user * exists but does not have any authorities, an empty set should be returned * @throws UnknownIdentityException if identity is not known * @throws AuthorityAccessException if unable to access authorities */ - Set getAuthorities(String dn) throws UnknownIdentityException, AuthorityAccessException; + Set getAuthorities(String identity) throws UnknownIdentityException, AuthorityAccessException; /** * Sets the specified authorities for the specified user. * - * @param dn the specified user + * @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user. * @param authorities the new authorities for the user * @throws UnknownIdentityException if identity is not known * @throws AuthorityAccessException if unable to access authorities */ - void setAuthorities(String dn, Set authorities) throws UnknownIdentityException, AuthorityAccessException; + void setAuthorities(String identity, Set authorities) throws UnknownIdentityException, AuthorityAccessException; /** * Gets the users for the specified authority. @@ -72,32 +72,32 @@ public interface AuthorityProvider { * Revokes the specified user. Its up to the implementor to determine the * semantics of revocation. * - * @param dn the dn of the user + * @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user. * @throws UnknownIdentityException if the user is not known * @throws AuthorityAccessException if unable to access the authorities */ - void revokeUser(String dn) throws UnknownIdentityException, AuthorityAccessException; + void revokeUser(String identity) throws UnknownIdentityException, AuthorityAccessException; /** * Add the specified user. * - * @param dn of the user + * @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user. * @param group Optional * @throws UnknownIdentityException if the user is not known * @throws AuthorityAccessException if unable to access the authorities */ - void addUser(String dn, String group) throws IdentityAlreadyExistsException, AuthorityAccessException; + void addUser(String identity, String group) throws IdentityAlreadyExistsException, AuthorityAccessException; /** * Gets the group for the specified user. Return null if the user does not * belong to a group. * - * @param dn the user + * @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user. * @return the group of the given user * @throws UnknownIdentityException if the user is not known * @throws AuthorityAccessException if unable to access the authorities */ - String getGroupForUser(String dn) throws UnknownIdentityException, AuthorityAccessException; + String getGroupForUser(String identity) throws UnknownIdentityException, AuthorityAccessException; /** * Revokes all users for a specified group. Its up to the implementor to @@ -112,21 +112,21 @@ public interface AuthorityProvider { /** * Adds the specified users to the specified group. * - * @param dn the set of users to add to the group + * @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user. * @param group to add users to * @throws UnknownIdentityException if the user is not known * @throws AuthorityAccessException if unable to access the authorities */ - void setUsersGroup(Set dn, String group) throws UnknownIdentityException, AuthorityAccessException; + void setUsersGroup(Set identity, String group) throws UnknownIdentityException, AuthorityAccessException; /** * Ungroups the specified user. * - * @param dn of the user + * @param identity of the user. The identity may be a dn, an email, a username, or any string that identities the user. * @throws UnknownIdentityException if the user is not known * @throws AuthorityAccessException if unable to access the authorities */ - void ungroupUser(String dn) throws UnknownIdentityException, AuthorityAccessException; + void ungroupUser(String identity) throws UnknownIdentityException, AuthorityAccessException; /** * Ungroups the specified group. Since the semantics of revocation is up to @@ -143,18 +143,18 @@ public interface AuthorityProvider { * 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 first identity in the chain is the end user that the request was issued on + * behalf of. The subsequent identities in the chain represent entities proxying * the user's request with the last being the proxy that sent the current * request. * - * @param dnChain of the user + * @param proxyChain proxy chain of user identities that for the download request * @param attributes of the flowfile being requested * @return the authorization result * @throws UnknownIdentityException if the user is not known * @throws AuthorityAccessException if unable to access the authorities */ - DownloadAuthorization authorizeDownload(List dnChain, Map attributes) throws UnknownIdentityException, AuthorityAccessException; + DownloadAuthorization authorizeDownload(List proxyChain, Map attributes) throws UnknownIdentityException, AuthorityAccessException; /** * Called immediately after instance creation for implementers to perform diff --git a/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationContext.java b/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationContext.java index ae32b10490..39bea4ffdc 100644 --- a/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationContext.java +++ b/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebConfigurationContext.java @@ -54,7 +54,7 @@ public interface NiFiWebConfigurationContext { void saveActions(NiFiWebRequestContext requestContext, Collection actions); /** - * @return the current user dn. Returns null if no user is found + * @return the current user identity. The value may be a DN, an email, a username, or any string that identities the user. Returns null if no user is found */ String getCurrentUserDn(); diff --git a/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebContext.java b/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebContext.java index 55e90e867e..96261e52c0 100644 --- a/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebContext.java +++ b/nifi-api/src/main/java/org/apache/nifi/web/NiFiWebContext.java @@ -51,7 +51,7 @@ public interface NiFiWebContext { void saveActions(Collection actions); /** - * @return the current user dn. Returns null if no user is found + * @return the current user identity. It may be a dn, an email, a username, or any string that identities the user. Returns null if no user is found */ String getCurrentUserDn(); diff --git a/nifi-assembly/LICENSE b/nifi-assembly/LICENSE index 5c499e3ec6..e21ecc95ad 100644 --- a/nifi-assembly/LICENSE +++ b/nifi-assembly/LICENSE @@ -374,6 +374,28 @@ For details see http://jqueryui.com OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +This product bundles 'jquery.base64.js' which is available under an MIT style license. + + Copyright (c) 2013 Yannick Albert (http://yckart.com/) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + This product bundles 'SlickGrid v2.2' which is available under an MIT style license. Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml index 961349f1ff..86aa8bc625 100644 --- a/nifi-assembly/pom.xml +++ b/nifi-assembly/pom.xml @@ -172,6 +172,11 @@ language governing permissions and limitations under the License. --> nifi-flume-nar nar + + org.apache.nifi + nifi-ldap-iaa-providers-nar + nar + org.apache.nifi nifi-dbcp-service-nar @@ -268,6 +273,7 @@ language governing permissions and limitations under the License. --> ./conf/flow.xml.gz ./conf/archive/ + ./conf/login-identity-providers.xml ./conf/authority-providers.xml ./conf/templates ./database_repository @@ -353,8 +359,10 @@ language governing permissions and limitations under the License. --> ./conf/authorized-users.xml 24 hours file-provider + + ROLE_MONITOR,ROLE_DFM,ROLE_ADMIN,ROLE_PROVENANCE,ROLE_NIFI diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java index a51d9bed99..809dd0921c 100644 --- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java +++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java @@ -25,10 +25,14 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,6 +49,7 @@ public class NiFiProperties extends Properties { public static final String FLOW_CONFIGURATION_FILE = "nifi.flow.configuration.file"; public static final String FLOW_CONFIGURATION_ARCHIVE_FILE = "nifi.flow.configuration.archive.file"; public static final String AUTHORITY_PROVIDER_CONFIGURATION_FILE = "nifi.authority.provider.configuration.file"; + public static final String LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE = "nifi.login.identity.provider.configuration.file"; public static final String REPOSITORY_DATABASE_DIRECTORY = "nifi.database.directory"; public static final String RESTORE_DIRECTORY = "nifi.restore.directory"; public static final String VERSION = "nifi.version"; @@ -126,11 +131,12 @@ public class NiFiProperties extends Properties { public static final String SECURITY_TRUSTSTORE_PASSWD = "nifi.security.truststorePasswd"; public static final String SECURITY_NEED_CLIENT_AUTH = "nifi.security.needClientAuth"; public static final String SECURITY_USER_AUTHORITY_PROVIDER = "nifi.security.user.authority.provider"; + public static final String SECURITY_USER_LOGIN_IDENTITY_PROVIDER = "nifi.security.user.login.identity.provider"; public static final String SECURITY_CLUSTER_AUTHORITY_PROVIDER_PORT = "nifi.security.cluster.authority.provider.port"; public static final String SECURITY_CLUSTER_AUTHORITY_PROVIDER_THREADS = "nifi.security.cluster.authority.provider.threads"; public static final String SECURITY_USER_CREDENTIAL_CACHE_DURATION = "nifi.security.user.credential.cache.duration"; public static final String SECURITY_SUPPORT_NEW_ACCOUNT_REQUESTS = "nifi.security.support.new.account.requests"; - public static final String SECURITY_DEFAULT_USER_ROLES = "nifi.security.default.user.roles"; + public static final String SECURITY_ANONYMOUS_AUTHORITIES = "nifi.security.anonymous.authorities"; public static final String SECURITY_OCSP_RESPONDER_URL = "nifi.security.ocsp.responder.url"; public static final String SECURITY_OCSP_RESPONDER_CERTIFICATE = "nifi.security.ocsp.responder.certificate"; @@ -187,6 +193,7 @@ public class NiFiProperties extends Properties { public static final String DEFAULT_TITLE = "NiFi"; public static final Boolean DEFAULT_AUTO_RESUME_STATE = true; public static final String DEFAULT_AUTHORITY_PROVIDER_CONFIGURATION_FILE = "conf/authority-providers.xml"; + public static final String DEFAULT_LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE = "conf/login-identity-providers.xml"; public static final String DEFAULT_USER_CREDENTIAL_CACHE_DURATION = "24 hours"; public static final Integer DEFAULT_REMOTE_INPUT_PORT = null; public static final Path DEFAULT_TEMPLATE_DIRECTORY = Paths.get("conf", "templates"); @@ -234,8 +241,7 @@ public class NiFiProperties extends Properties { } /** - * This is the method through which the NiFiProperties object should be - * obtained. + * This is the method through which the NiFiProperties object should be obtained. * * @return the NiFiProperties object to use * @throws RuntimeException if unable to load properties file @@ -424,8 +430,7 @@ public class NiFiProperties extends Properties { } /** - * Returns whether the processors should be started automatically when the - * application loads. + * Returns whether the processors should be started automatically when the application loads. * * @return Whether to auto start the processors or not */ @@ -436,8 +441,7 @@ public class NiFiProperties extends Properties { } /** - * Returns the number of partitions that should be used for the FlowFile - * Repository + * Returns the number of partitions that should be used for the FlowFile Repository * * @return the number of partitions */ @@ -448,8 +452,7 @@ public class NiFiProperties extends Properties { } /** - * Returns the number of milliseconds between FlowFileRepository - * checkpointing + * Returns the number of milliseconds between FlowFileRepository checkpointing * * @return the number of milliseconds between checkpoint events */ @@ -482,6 +485,18 @@ public class NiFiProperties extends Properties { } } + /** + * @return the user authorities file + */ + public File getLoginIdentityProviderConfiguraitonFile() { + final String value = getProperty(LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE); + if (StringUtils.isBlank(value)) { + return new File(DEFAULT_LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE); + } else { + return new File(value); + } + } + /** * Will default to true unless the value is explicitly set to false. * @@ -510,6 +525,19 @@ public class NiFiProperties extends Properties { return shouldSupport; } + public Set getAnonymousAuthorities() { + final Set authorities; + + final String rawAnonymousAuthorities = getProperty(SECURITY_ANONYMOUS_AUTHORITIES); + if (!StringUtils.isEmpty(rawAnonymousAuthorities)) { + authorities = new HashSet<>(Arrays.asList(rawAnonymousAuthorities.split(","))); + } else { + authorities = Collections.EMPTY_SET; + } + + return authorities; + } + // getters for web properties // public Integer getPort() { Integer port = null; @@ -851,8 +879,7 @@ public class NiFiProperties extends Properties { } /** - * Returns the database repository path. It simply returns the value - * configured. No directories will be created as a result of this operation. + * Returns the database repository path. It simply returns the value configured. No directories will be created as a result of this operation. * * @return database repository path * @throws InvalidPathException If the configured path is invalid @@ -862,8 +889,7 @@ public class NiFiProperties extends Properties { } /** - * Returns the flow file repository path. It simply returns the value - * configured. No directories will be created as a result of this operation. + * Returns the flow file repository path. It simply returns the value configured. No directories will be created as a result of this operation. * * @return database repository path * @throws InvalidPathException If the configured path is invalid @@ -873,10 +899,8 @@ public class NiFiProperties extends Properties { } /** - * Returns the content repository paths. This method returns a mapping of - * file repository name to file repository paths. It simply returns the - * values configured. No directories will be created as a result of this - * operation. + * Returns the content repository paths. This method returns a mapping of file repository name to file repository paths. It simply returns the values configured. No directories will be created as + * a result of this operation. * * @return file repositories paths * @throws InvalidPathException If any of the configured paths are invalid @@ -900,10 +924,8 @@ public class NiFiProperties extends Properties { } /** - * Returns the provenance repository paths. This method returns a mapping of - * file repository name to file repository paths. It simply returns the - * values configured. No directories will be created as a result of this - * operation. + * Returns the provenance repository paths. This method returns a mapping of file repository name to file repository paths. It simply returns the values configured. No directories will be created + * as a result of this operation. * * @return the name and paths of all provenance repository locations */ diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java index 51269335ed..6236d8e3d4 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java @@ -34,8 +34,7 @@ public final class CertificateUtils { private static final Logger logger = LoggerFactory.getLogger(CertificateUtils.class); /** - * Returns true if the given keystore can be loaded using the given keystore - * type and password. Returns false otherwise. + * Returns true if the given keystore can be loaded using the given keystore type and password. Returns false otherwise. * * @param keystore the keystore to validate * @param keystoreType the type of the keystore @@ -77,40 +76,29 @@ public final class CertificateUtils { } /** - * Extracts the username from the specified DN. If the username cannot be - * extracted because the CN is in an unrecognized format, the entire CN is - * returned. If the CN cannot be extracted because the DN is in an - * unrecognized format, the entire DN is returned. + * Extracts the username from the specified DN. If the username cannot be extracted because the CN is in an unrecognized format, the entire CN is returned. If the CN cannot be extracted because + * the DN is in an unrecognized format, the entire DN is returned. * * @param dn the dn to extract the username from * @return the exatracted username */ public static String extractUsername(String dn) { String username = dn; - String cn = ""; // ensure the dn is specified if (StringUtils.isNotBlank(dn)) { + // determine the separate + final String separator = StringUtils.indexOfIgnoreCase(dn, "/cn=") > 0 ? "/" : ","; - // attempt to locate the cn - if (dn.startsWith("CN=")) { - cn = StringUtils.substringBetween(dn, "CN=", ","); - } else if (dn.startsWith("/CN=")) { - cn = StringUtils.substringBetween(dn, "CN=", "/"); - } else if (dn.startsWith("C=") || dn.startsWith("/C=")) { - cn = StringUtils.substringAfter(dn, "CN="); - } else if (dn.startsWith("/") && StringUtils.contains(dn, "CN=")) { - cn = StringUtils.substringAfter(dn, "CN="); - } - - // attempt to get the username from the cn - if (StringUtils.isNotBlank(cn)) { - if (cn.endsWith(")")) { - username = StringUtils.substringBetween(cn, "(", ")"); - } else if (cn.contains(" ")) { - username = StringUtils.substringAfterLast(cn, " "); + // attempt to locate the cd + final String cnPattern = "cn="; + final int cnIndex = StringUtils.indexOfIgnoreCase(dn, cnPattern); + if (cnIndex >= 0) { + int separatorIndex = StringUtils.indexOf(dn, separator, cnIndex); + if (separatorIndex > 0) { + username = StringUtils.substring(dn, cnIndex + cnPattern.length(), separatorIndex); } else { - username = cn; + username = StringUtils.substring(dn, cnIndex + cnPattern.length()); } } } @@ -119,9 +107,7 @@ public final class CertificateUtils { } /** - * Returns a list of subject alternative names. Any name that is represented - * as a String by X509Certificate.getSubjectAlternativeNames() is converted - * to lowercase and returned. + * Returns a list of subject alternative names. Any name that is represented as a String by X509Certificate.getSubjectAlternativeNames() is converted to lowercase and returned. * * @param certificate a certificate * @return a list of subject alternative names; list is never null @@ -137,12 +123,9 @@ public final class CertificateUtils { final List result = new ArrayList<>(); for (final List generalName : altNames) { /** - * generalName has the name type as the first element a String or - * byte array for the second element. We return any general names - * that are String types. + * generalName has the name type as the first element a String or byte array for the second element. We return any general names that are String types. * - * We don't inspect the numeric name type because some certificates - * incorrectly put IPs and DNS names under the wrong name types. + * We don't inspect the numeric name type because some certificates incorrectly put IPs and DNS names under the wrong name types. */ final Object value = generalName.get(1); if (value instanceof String) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml index aeaeda8024..b1176182f3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/pom.xml @@ -53,7 +53,6 @@ **/authorization/generated/*.java, - diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/AuditDataSourceFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/AuditDataSourceFactoryBean.java index aeb2755276..87cd420fd3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/AuditDataSourceFactoryBean.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/AuditDataSourceFactoryBean.java @@ -45,8 +45,8 @@ public class AuditDataSourceFactoryBean implements FactoryBean { // ------------ private static final String CREATE_ACTION_TABLE = "CREATE TABLE ACTION (" + "ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, " - + "USER_DN VARCHAR2(255) NOT NULL, " - + "USER_NAME VARCHAR2(100) NOT NULL, " + + "IDENTITY VARCHAR2(4096) NOT NULL, " + + "USER_NAME VARCHAR2(4096) NOT NULL, " + "SOURCE_ID VARCHAR2(100) NOT NULL, " + "SOURCE_NAME VARCHAR2(1000) NOT NULL, " + "SOURCE_TYPE VARCHAR2(1000) NOT NULL, " @@ -107,6 +107,10 @@ public class AuditDataSourceFactoryBean implements FactoryBean { + "FOREIGN KEY (ACTION_ID) REFERENCES ACTION(ID)" + ")"; + private static final String RENAME_DN_COLUMN = "ALTER TABLE ACTION ALTER COLUMN USER_DN RENAME TO IDENTITY"; + private static final String RESIZE_IDENTITY_COLUMN = "ALTER TABLE ACTION MODIFY IDENTITY VARCHAR(4096)"; + private static final String RESIZE_USER_NAME_COLUMN = "ALTER TABLE ACTION MODIFY USER_NAME VARCHAR(4096)"; + private JdbcConnectionPool connectionPool; private NiFiProperties properties; @@ -148,15 +152,15 @@ public class AuditDataSourceFactoryBean implements FactoryBean { connection = connectionPool.getConnection(); connection.setAutoCommit(false); + // create a statement for initializing the database + statement = connection.createStatement(); + // determine if the tables need to be created rs = connection.getMetaData().getTables(null, null, "ACTION", null); if (!rs.next()) { logger.info("Database not built for repository: " + databaseUrl + ". Building now..."); RepositoryUtils.closeQuietly(rs); - // create a statement for initializing the database - statement = connection.createStatement(); - // action table statement.execute(CREATE_ACTION_TABLE); @@ -171,6 +175,15 @@ public class AuditDataSourceFactoryBean implements FactoryBean { statement.execute(CREATE_PURGE_DETAILS_TABLE); } else { logger.info("Existing database found and connected to at: " + databaseUrl); + RepositoryUtils.closeQuietly(rs); + + // check if the DN column exists to see if we need to transform the table + rs = connection.getMetaData().getColumns(null, null, "ACTION", "USER_DN"); + if (rs.next()) { + statement.execute(RENAME_DN_COLUMN); + statement.execute(RESIZE_IDENTITY_COLUMN); + statement.execute(RESIZE_USER_NAME_COLUMN); + } } // commit any changes diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/KeyDataSourceFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/KeyDataSourceFactoryBean.java new file mode 100644 index 0000000000..688aa5ad24 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/KeyDataSourceFactoryBean.java @@ -0,0 +1,154 @@ +/* + * 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; + +import java.io.File; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.apache.commons.lang3.StringUtils; +import org.h2.jdbcx.JdbcConnectionPool; +import org.apache.nifi.util.NiFiProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.FactoryBean; + +/** + * + */ +public class KeyDataSourceFactoryBean implements FactoryBean { + + private static final Logger logger = LoggerFactory.getLogger(KeyDataSourceFactoryBean.class); + private static final String NF_USERNAME_PASSWORD = "nf"; + private static final int MAX_CONNECTIONS = 5; + + // database file name + private static final String KEY_DATABASE_FILE_NAME = "nifi-key"; + + // ---------- + // keys table + // ---------- + private static final String CREATE_KEY_TABLE = "CREATE TABLE KEY (" + + "ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, " + + "IDENTITY VARCHAR2(4096) NOT NULL UNIQUE, " + + "KEY VARCHAR2(100) NOT NULL" + + ")"; + + private JdbcConnectionPool connectionPool; + + private NiFiProperties properties; + + @Override + public Object getObject() throws Exception { + if (connectionPool == null) { + + // locate the repository directory + String repositoryDirectoryPath = properties.getProperty(NiFiProperties.REPOSITORY_DATABASE_DIRECTORY); + + // ensure the repository directory is specified + if (repositoryDirectoryPath == null) { + throw new NullPointerException("Database directory must be specified."); + } + + // create a handle to the repository directory + File repositoryDirectory = new File(repositoryDirectoryPath); + + // get a handle to the database file + File databaseFile = new File(repositoryDirectory, KEY_DATABASE_FILE_NAME); + + // format the database url + String databaseUrl = "jdbc:h2:" + databaseFile + ";AUTOCOMMIT=OFF;DB_CLOSE_ON_EXIT=FALSE;LOCK_MODE=3"; + String databaseUrlAppend = properties.getProperty(NiFiProperties.H2_URL_APPEND); + if (StringUtils.isNotBlank(databaseUrlAppend)) { + databaseUrl += databaseUrlAppend; + } + + // create the pool + connectionPool = JdbcConnectionPool.create(databaseUrl, NF_USERNAME_PASSWORD, NF_USERNAME_PASSWORD); + connectionPool.setMaxConnections(MAX_CONNECTIONS); + + Connection connection = null; + ResultSet rs = null; + Statement statement = null; + try { + // get a connection + connection = connectionPool.getConnection(); + connection.setAutoCommit(false); + + // create a statement for initializing the database + statement = connection.createStatement(); + + // determine if the tables need to be created + rs = connection.getMetaData().getTables(null, null, "KEY", null); + if (!rs.next()) { + logger.info("Database not built for repository: " + databaseUrl + ". Building now..."); + RepositoryUtils.closeQuietly(rs); + + // action table + statement.execute(CREATE_KEY_TABLE); + } else { + logger.info("Existing database found and connected to at: " + databaseUrl); + } + + // commit any changes + connection.commit(); + } catch (SQLException sqle) { + RepositoryUtils.rollback(connection, logger); + throw sqle; + } finally { + RepositoryUtils.closeQuietly(rs); + RepositoryUtils.closeQuietly(statement); + RepositoryUtils.closeQuietly(connection); + } + } + + return connectionPool; + } + + @Override + public Class getObjectType() { + return JdbcConnectionPool.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + public void setProperties(NiFiProperties properties) { + this.properties = properties; + } + + /** + * Disposes resources. + */ + public void shutdown() { + + // shutdown the connection pool + if (connectionPool != null) { + try { + connectionPool.dispose(); + } catch (Exception e) { + logger.warn("Unable to dispose of connection pool: " + e.getMessage()); + if (logger.isDebugEnabled()) { + logger.warn(StringUtils.EMPTY, e); + } + } + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/UserDataSourceFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/UserDataSourceFactoryBean.java index ebcf574107..6d8566e8ad 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/UserDataSourceFactoryBean.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/UserDataSourceFactoryBean.java @@ -21,8 +21,11 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.Authority; import org.h2.jdbcx.JdbcConnectionPool; import org.apache.nifi.user.NiFiUser; import org.apache.nifi.util.NiFiProperties; @@ -41,8 +44,8 @@ public class UserDataSourceFactoryBean implements FactoryBean { private static final String CREATE_USER_TABLE = "CREATE TABLE USER (" + "ID VARCHAR2(100) NOT NULL PRIMARY KEY, " - + "DN VARCHAR2(255) NOT NULL UNIQUE, " - + "USER_NAME VARCHAR2(100) NOT NULL, " + + "IDENTITY VARCHAR2(4096) NOT NULL UNIQUE, " + + "USER_NAME VARCHAR2(4096) NOT NULL, " + "USER_GROUP VARCHAR2(100), " + "CREATION TIMESTAMP NOT NULL, " + "LAST_ACCESSED TIMESTAMP, " @@ -60,57 +63,30 @@ public class UserDataSourceFactoryBean implements FactoryBean { + ")"; private static final String INSERT_ANONYMOUS_USER = "INSERT INTO USER (" - + "ID, DN, USER_NAME, CREATION, LAST_VERIFIED, JUSTIFICATION, STATUS" + + "ID, IDENTITY, USER_NAME, CREATION, LAST_VERIFIED, JUSTIFICATION, STATUS" + ") VALUES (" + "'" + UUID.randomUUID().toString() + "', " - + "'" + NiFiUser.ANONYMOUS_USER_DN + "', " - + "'" + NiFiUser.ANONYMOUS_USER_DN + "', " + + "'" + NiFiUser.ANONYMOUS_USER_IDENTITY + "', " + + "'" + NiFiUser.ANONYMOUS_USER_IDENTITY + "', " + "NOW(), " + "NOW(), " + "'Anonymous user needs no justification', " + "'ACTIVE'" + ")"; - private static final String INSERT_ANONYMOUS_MONITOR_AUTHORITY = "INSERT INTO AUTHORITY (" + private static final String INSERT_ANONYMOUS_AUTHORITY = "INSERT INTO AUTHORITY (" + "USER_ID, ROLE" + ") VALUES (" - + "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), " - + "'ROLE_MONITOR'" + + "(SELECT ID FROM USER WHERE IDENTITY = '" + NiFiUser.ANONYMOUS_USER_IDENTITY + "'), " + + "'%s'" + ")"; - private static final String INSERT_ANONYMOUS_DFM_AUTHORITY = "INSERT INTO AUTHORITY (" - + "USER_ID, ROLE" - + ") VALUES (" - + "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), " - + "'ROLE_DFM'" - + ")"; + private static final String DELETE_ANONYMOUS_AUTHORITIES = "DELETE FROM AUTHORITY " + + "WHERE USER_ID = (SELECT ID FROM USER WHERE IDENTITY = '" + NiFiUser.ANONYMOUS_USER_IDENTITY + "')"; - private static final String INSERT_ANONYMOUS_ADMIN_AUTHORITY = "INSERT INTO AUTHORITY (" - + "USER_ID, ROLE" - + ") VALUES (" - + "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), " - + "'ROLE_ADMIN'" - + ")"; - - private static final String INSERT_ANONYMOUS_NIFI_AUTHORITY = "INSERT INTO AUTHORITY (" - + "USER_ID, ROLE" - + ") VALUES (" - + "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), " - + "'ROLE_NIFI'" - + ")"; - - private static final String INSERT_ANONYMOUS_PROVENANCE_AUTHORITY = "INSERT INTO AUTHORITY (" - + "USER_ID, ROLE" - + ") VALUES (" - + "(SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "'), " - + "'ROLE_PROVENANCE'" - + ")"; - - private static final String SELECT_ANONYMOUS_PROVENANCE_AUTHORITY = "SELECT * FROM AUTHORITY " - + "WHERE " - + "USER_ID = (SELECT ID FROM USER WHERE DN = '" + NiFiUser.ANONYMOUS_USER_DN + "') " - + "AND " - + "ROLE = 'ROLE_PROVENANCE'"; + private static final String RENAME_DN_COLUMN = "ALTER TABLE USER ALTER COLUMN DN RENAME TO IDENTITY"; + private static final String RESIZE_IDENTITY_COLUMN = "ALTER TABLE USER MODIFY IDENTITY VARCHAR(4096)"; + private static final String RESIZE_USER_NAME_COLUMN = "ALTER TABLE USER MODIFY USER_NAME VARCHAR(4096)"; private JdbcConnectionPool connectionPool; @@ -128,6 +104,17 @@ public class UserDataSourceFactoryBean implements FactoryBean { throw new NullPointerException("Database directory must be specified."); } + // get the roles being granted to anonymous users + final Set rawAnonymousAuthorities = new HashSet<>(properties.getAnonymousAuthorities()); + final Set anonymousAuthorities = Authority.convertRawAuthorities(rawAnonymousAuthorities); + + // ensure every authorities was recognized + if (rawAnonymousAuthorities.size() != anonymousAuthorities.size()) { + final Set validAuthorities = Authority.convertAuthorities(anonymousAuthorities); + rawAnonymousAuthorities.removeAll(validAuthorities); + throw new IllegalStateException("Invalid authorities specified: " + StringUtils.join(rawAnonymousAuthorities, ", ")); + } + // create a handle to the repository directory File repositoryDirectory = new File(repositoryDirectoryPath); @@ -161,21 +148,25 @@ public class UserDataSourceFactoryBean implements FactoryBean { // seed the anonymous user statement.execute(INSERT_ANONYMOUS_USER); - statement.execute(INSERT_ANONYMOUS_MONITOR_AUTHORITY); - statement.execute(INSERT_ANONYMOUS_DFM_AUTHORITY); - statement.execute(INSERT_ANONYMOUS_ADMIN_AUTHORITY); - statement.execute(INSERT_ANONYMOUS_NIFI_AUTHORITY); } else { logger.info("Existing database found and connected to at: " + databaseUrl); + RepositoryUtils.closeQuietly(rs); + + // if the DN column exists, transform the table + rs = connection.getMetaData().getColumns(null, null, "USER", "DN"); + if (rs.next()) { + statement.execute(RENAME_DN_COLUMN); + statement.execute(RESIZE_IDENTITY_COLUMN); + statement.execute(RESIZE_USER_NAME_COLUMN); + } + + // remove all authorities for the anonymous user + statement.execute(DELETE_ANONYMOUS_AUTHORITIES); } - // close the previous result set - RepositoryUtils.closeQuietly(rs); - - // merge in the provenance role to handle existing databases - rs = statement.executeQuery(SELECT_ANONYMOUS_PROVENANCE_AUTHORITY); - if (!rs.next()) { - statement.execute(INSERT_ANONYMOUS_PROVENANCE_AUTHORITY); + // add all authorities for the anonymous user + for (final Authority authority : anonymousAuthorities) { + statement.execute(String.format(INSERT_ANONYMOUS_AUTHORITY, authority.name())); } // commit any changes diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/DAOFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/DAOFactory.java index dee4ef916d..eb7e3ce6a9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/DAOFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/DAOFactory.java @@ -26,4 +26,6 @@ public interface DAOFactory { ActionDAO getActionDAO(); AuthorityDAO getAuthorityDAO(); + + KeyDAO getKeyDAO(); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java new file mode 100644 index 0000000000..2a24e0b08f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/KeyDAO.java @@ -0,0 +1,49 @@ +/* + * 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.dao; + +import org.apache.nifi.key.Key; + +/** + * Key data access. + */ +public interface KeyDAO { + + /** + * Gets the key for the specified user identity. Returns null if no key exists for the key id. + * + * @param id The key id + * @return The key or null + */ + Key findKeyById(int id); + + /** + * Gets the latest key for the specified identity. Returns null if no key exists for the user identity. + * + * @param identity The identity + * @return The key or null + */ + Key findLatestKeyByIdentity(String identity); + + /** + * Creates a key for the specified user identity. + * + * @param identity The user identity + * @return The key + */ + Key createKey(String identity); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/DAOFactoryImpl.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/DAOFactoryImpl.java index 2f3de0edff..940e364230 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/DAOFactoryImpl.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/DAOFactoryImpl.java @@ -20,6 +20,7 @@ import java.sql.Connection; import org.apache.nifi.admin.dao.ActionDAO; import org.apache.nifi.admin.dao.AuthorityDAO; import org.apache.nifi.admin.dao.DAOFactory; +import org.apache.nifi.admin.dao.KeyDAO; import org.apache.nifi.admin.dao.UserDAO; /** @@ -48,4 +49,9 @@ public class DAOFactoryImpl implements DAOFactory { return new StandardUserDAO(connection); } + @Override + public KeyDAO getKeyDAO() { + return new StandardKeyDAO(connection); + } + } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardActionDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardActionDAO.java index fe693e19dc..8fdfd3494f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardActionDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardActionDAO.java @@ -63,7 +63,7 @@ public class StandardActionDAO implements ActionDAO { // action table // ------------ private static final String INSERT_ACTION = "INSERT INTO ACTION (" - + "USER_DN, USER_NAME, SOURCE_ID, SOURCE_NAME, SOURCE_TYPE, OPERATION, ACTION_TIMESTAMP" + + "IDENTITY, USER_NAME, SOURCE_ID, SOURCE_NAME, SOURCE_TYPE, OPERATION, ACTION_TIMESTAMP" + ") VALUES (" + "?, " + "?, " @@ -216,8 +216,8 @@ public class StandardActionDAO implements ActionDAO { try { // obtain a statement to insert to the action table statement = connection.prepareStatement(INSERT_ACTION, Statement.RETURN_GENERATED_KEYS); - statement.setString(1, StringUtils.left(action.getUserIdentity(), 255)); - statement.setString(2, StringUtils.left(action.getUserName(), 100)); + statement.setString(1, StringUtils.left(action.getUserIdentity(), 4096)); + statement.setString(2, StringUtils.left(action.getUserName(), 4096)); statement.setString(3, action.getSourceId()); statement.setString(4, StringUtils.left(action.getSourceName(), 1000)); statement.setString(5, action.getSourceType().toString()); @@ -561,7 +561,7 @@ public class StandardActionDAO implements ActionDAO { FlowChangeAction action = new FlowChangeAction(); action.setId(actionId); - action.setUserIdentity(rs.getString("USER_DN")); + action.setUserIdentity(rs.getString("IDENTITY")); action.setUserName(rs.getString("USER_NAME")); action.setOperation(Operation.valueOf(rs.getString("OPERATION"))); action.setTimestamp(new Date(rs.getTimestamp("ACTION_TIMESTAMP").getTime())); @@ -635,7 +635,7 @@ public class StandardActionDAO implements ActionDAO { // populate the action action = new FlowChangeAction(); action.setId(rs.getInt("ID")); - action.setUserIdentity(rs.getString("USER_DN")); + action.setUserIdentity(rs.getString("IDENTITY")); action.setUserName(rs.getString("USER_NAME")); action.setOperation(operation); action.setTimestamp(new Date(rs.getTimestamp("ACTION_TIMESTAMP").getTime())); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java new file mode 100644 index 0000000000..f4bdc1d3c9 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardKeyDAO.java @@ -0,0 +1,154 @@ +/* + * 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.dao.impl; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.UUID; +import org.apache.nifi.admin.RepositoryUtils; +import org.apache.nifi.admin.dao.DataAccessException; +import org.apache.nifi.admin.dao.KeyDAO; +import org.apache.nifi.key.Key; + +/** + * + */ +public class StandardKeyDAO implements KeyDAO { + + private static final String SELECT_KEY_FOR_USER_BY_ID = "SELECT ID, IDENTITY, KEY " + + "FROM KEY " + + "WHERE ID = ?"; + + private static final String SELECT_KEY_FOR_USER_BY_IDENTITY = "SELECT ID, IDENTITY, KEY " + + "FROM KEY " + + "WHERE IDENTITY = ?"; + + private static final String INSERT_KEY = "INSERT INTO KEY (" + + "IDENTITY, KEY" + + ") VALUES (" + + "?, ?" + + ")"; + + private final Connection connection; + + public StandardKeyDAO(Connection connection) { + this.connection = connection; + } + + @Override + public Key findKeyById(int id) { + Key key = null; + + PreparedStatement statement = null; + ResultSet rs = null; + try { + // add each authority for the specified user + statement = connection.prepareStatement(SELECT_KEY_FOR_USER_BY_ID); + statement.setInt(1, id); + + // execute the query + rs = statement.executeQuery(); + + // if the key was found, add it + if (rs.next()) { + key = new Key(); + key.setId(rs.getInt("ID")); + key.setIdentity(rs.getString("IDENTITY")); + key.setKey(rs.getString("KEY")); + } + } catch (SQLException sqle) { + throw new DataAccessException(sqle); + } finally { + RepositoryUtils.closeQuietly(rs); + RepositoryUtils.closeQuietly(statement); + } + + return key; + } + + @Override + public Key findLatestKeyByIdentity(String identity) { + if (identity == null) { + throw new IllegalArgumentException("Specified identity cannot be null."); + } + + Key key = null; + + PreparedStatement statement = null; + ResultSet rs = null; + try { + // add each authority for the specified user + statement = connection.prepareStatement(SELECT_KEY_FOR_USER_BY_IDENTITY); + statement.setString(1, identity); + + // execute the query + rs = statement.executeQuery(); + + // if the key was found, add it + if (rs.next()) { + key = new Key(); + key.setId(rs.getInt("ID")); + key.setIdentity(rs.getString("IDENTITY")); + key.setKey(rs.getString("KEY")); + } + } catch (SQLException sqle) { + throw new DataAccessException(sqle); + } finally { + RepositoryUtils.closeQuietly(rs); + RepositoryUtils.closeQuietly(statement); + } + + return key; + } + + @Override + public Key createKey(final String identity) { + PreparedStatement statement = null; + ResultSet rs = null; + try { + final String keyValue = UUID.randomUUID().toString(); + + // add each authority for the specified user + statement = connection.prepareStatement(INSERT_KEY, Statement.RETURN_GENERATED_KEYS); + statement.setString(1, identity); + statement.setString(2, keyValue); + + // insert the key + int updateCount = statement.executeUpdate(); + rs = statement.getGeneratedKeys(); + + // verify the results + if (updateCount == 1 && rs.next()) { + final Key key = new Key(); + key.setId(rs.getInt(1)); + key.setIdentity(identity); + key.setKey(keyValue); + return key; + } else { + throw new DataAccessException("Unable to add key for user."); + } + } catch (SQLException sqle) { + throw new DataAccessException(sqle); + } finally { + RepositoryUtils.closeQuietly(rs); + RepositoryUtils.closeQuietly(statement); + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardUserDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardUserDAO.java index f6a62df53a..20356e32f0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardUserDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/dao/impl/StandardUserDAO.java @@ -45,9 +45,9 @@ public class StandardUserDAO implements UserDAO { + "FROM USER U " + "WHERE U.STATUS = 'PENDING'"; - private static final String SELECT_USER_BY_DN = "SELECT " + private static final String SELECT_USER_BY_USER = "SELECT " + "U.ID, " - + "U.DN, " + + "U.IDENTITY, " + "U.USER_NAME, " + "U.USER_GROUP, " + "U.CREATION, " @@ -59,11 +59,11 @@ public class StandardUserDAO implements UserDAO { + "FROM USER U " + "LEFT JOIN AUTHORITY A " // ensures that users without authorities are still matched + "ON U.ID = A.USER_ID " - + "WHERE U.DN = ?"; + + "WHERE U.IDENTITY = ?"; private static final String SELECT_USER_BY_ID = "SELECT " + "U.ID, " - + "U.DN, " + + "U.IDENTITY, " + "U.USER_NAME, " + "U.USER_GROUP, " + "U.CREATION, " @@ -79,7 +79,7 @@ public class StandardUserDAO implements UserDAO { private static final String SELECT_USERS = "SELECT " + "U.ID, " - + "U.DN, " + + "U.IDENTITY, " + "U.USER_NAME, " + "U.USER_GROUP, " + "U.CREATION, " @@ -91,7 +91,7 @@ public class StandardUserDAO implements UserDAO { + "FROM USER U " + "LEFT JOIN AUTHORITY A " // ensures that users without authorities are still matched + "ON U.ID = A.USER_ID " - + "WHERE U.DN <> ?"; + + "WHERE U.IDENTITY <> ?"; private static final String SELECT_USER_GROUPS = "SELECT DISTINCT " + "U.USER_GROUP " @@ -99,7 +99,7 @@ public class StandardUserDAO implements UserDAO { private static final String SELECT_USER_GROUP = "SELECT " + "U.ID, " - + "U.DN, " + + "U.IDENTITY, " + "U.USER_NAME, " + "U.USER_GROUP, " + "U.CREATION, " @@ -111,10 +111,10 @@ public class StandardUserDAO implements UserDAO { + "FROM USER U " + "LEFT JOIN AUTHORITY A " // ensures that users without authorities are still matched + "ON U.ID = A.USER_ID " - + "WHERE U.DN <> ? AND U.USER_GROUP = ?"; + + "WHERE U.IDENTITY <> ? AND U.USER_GROUP = ?"; private static final String INSERT_USER = "INSERT INTO USER (" - + "ID, DN, USER_NAME, USER_GROUP, CREATION, LAST_VERIFIED, JUSTIFICATION, STATUS" + + "ID, IDENTITY, USER_NAME, USER_GROUP, CREATION, LAST_VERIFIED, JUSTIFICATION, STATUS" + ") VALUES (" + "?, " + "?, " @@ -127,7 +127,7 @@ public class StandardUserDAO implements UserDAO { + ")"; private static final String UPDATE_USER = "UPDATE USER SET " - + "DN = ?, " + + "IDENTITY = ?, " + "USER_NAME = ?, " + "USER_GROUP = ?, " + "LAST_ACCESSED = ?, " @@ -194,7 +194,7 @@ public class StandardUserDAO implements UserDAO { try { // create the connection and obtain a statement statement = connection.prepareStatement(SELECT_USERS); - statement.setString(1, NiFiUser.ANONYMOUS_USER_DN); + statement.setString(1, NiFiUser.ANONYMOUS_USER_IDENTITY); // execute the query rs = statement.executeQuery(); @@ -211,7 +211,7 @@ public class StandardUserDAO implements UserDAO { if (user == null || !userId.equals(user.getId())) { user = new NiFiUser(); user.setId(userId); - user.setDn(rs.getString("DN")); + user.setIdentity(rs.getString("IDENTITY")); user.setUserName(rs.getString("USER_NAME")); user.setUserGroup(rs.getString("USER_GROUP")); user.setJustification(rs.getString("JUSTIFICATION")); @@ -287,7 +287,7 @@ public class StandardUserDAO implements UserDAO { try { // create the connection and obtain a statement statement = connection.prepareStatement(SELECT_USER_GROUP); - statement.setString(1, NiFiUser.ANONYMOUS_USER_DN); + statement.setString(1, NiFiUser.ANONYMOUS_USER_IDENTITY); statement.setString(2, group); // execute the query @@ -305,7 +305,7 @@ public class StandardUserDAO implements UserDAO { if (user == null || !userId.equals(user.getId())) { user = new NiFiUser(); user.setId(userId); - user.setDn(rs.getString("DN")); + user.setIdentity(rs.getString("IDENTITY")); user.setUserName(rs.getString("USER_NAME")); user.setUserGroup(rs.getString("USER_GROUP")); user.setJustification(rs.getString("JUSTIFICATION")); @@ -366,7 +366,7 @@ public class StandardUserDAO implements UserDAO { if (user == null) { user = new NiFiUser(); user.setId(rs.getString("ID")); - user.setDn(rs.getString("DN")); + user.setIdentity(rs.getString("IDENTITY")); user.setUserName(rs.getString("USER_NAME")); user.setUserGroup(rs.getString("USER_GROUP")); user.setJustification(rs.getString("JUSTIFICATION")); @@ -409,7 +409,7 @@ public class StandardUserDAO implements UserDAO { ResultSet rs = null; try { // create the connection and obtain a statement - statement = connection.prepareStatement(SELECT_USER_BY_DN); + statement = connection.prepareStatement(SELECT_USER_BY_USER); statement.setString(1, dn); // execute the query @@ -424,7 +424,7 @@ public class StandardUserDAO implements UserDAO { if (user == null) { user = new NiFiUser(); user.setId(rs.getString("ID")); - user.setDn(rs.getString("DN")); + user.setIdentity(rs.getString("IDENTITY")); user.setUserName(rs.getString("USER_NAME")); user.setUserGroup(rs.getString("USER_GROUP")); user.setJustification(rs.getString("JUSTIFICATION")); @@ -463,20 +463,25 @@ public class StandardUserDAO implements UserDAO { @Override public NiFiUser createUser(NiFiUser user) throws DataAccessException { - if (user.getDn() == null) { - throw new IllegalArgumentException("User dn must be specified."); + if (user.getIdentity() == null) { + throw new IllegalArgumentException("User identity must be specified."); + } + + // ensure the user identity is not too lengthy + if (user.getIdentity().length() > 4096) { + throw new IllegalArgumentException("User identity must be less than 4096 characters."); } PreparedStatement statement = null; ResultSet rs = null; try { - final String id = UUID.nameUUIDFromBytes(user.getDn().getBytes(StandardCharsets.UTF_8)).toString(); + final String id = UUID.nameUUIDFromBytes(user.getIdentity().getBytes(StandardCharsets.UTF_8)).toString(); // create a statement statement = connection.prepareStatement(INSERT_USER, Statement.RETURN_GENERATED_KEYS); statement.setString(1, id); - statement.setString(2, StringUtils.left(user.getDn(), 255)); - statement.setString(3, StringUtils.left(user.getUserName(), 100)); + statement.setString(2, StringUtils.left(user.getIdentity(), 4096)); + statement.setString(3, StringUtils.left(user.getUserName(), 4096)); statement.setString(4, StringUtils.left(user.getUserGroup(), 100)); if (user.getLastVerified() != null) { statement.setTimestamp(5, new java.sql.Timestamp(user.getLastVerified().getTime())); @@ -531,8 +536,8 @@ public class StandardUserDAO implements UserDAO { try { // create a statement statement = connection.prepareStatement(UPDATE_USER); - statement.setString(1, StringUtils.left(user.getDn(), 255)); - statement.setString(2, StringUtils.left(user.getUserName(), 100)); + statement.setString(1, StringUtils.left(user.getIdentity(), 4096)); + statement.setString(2, StringUtils.left(user.getUserName(), 4096)); statement.setString(3, StringUtils.left(user.getUserGroup(), 100)); statement.setString(6, StringUtils.left(user.getJustification(), 500)); statement.setString(7, user.getStatus().toString()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java new file mode 100644 index 0000000000..ae64c414c3 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/KeyService.java @@ -0,0 +1,42 @@ +/* + * 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; + +import org.apache.nifi.key.Key; + +/** + * Supports retrieving and issues keys for signing user tokens. + */ +public interface KeyService { + + /** + * Gets a key for the specified user identity. Returns null if the user has not had a key issued + * + * @param id The key id + * @return The key or null + */ + Key getKey(int id); + + /** + * Gets a key for the specified user identity. If a key does not exist, one will be created. + * + * @param identity The user identity + * @return The key + * @throws AdministrationException if it failed to get/create the key + */ + Key getOrCreateKey(String identity); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/AbstractUserAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/AbstractUserAction.java index b970dc18c3..69c6c1f59a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/AbstractUserAction.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/AbstractUserAction.java @@ -76,14 +76,14 @@ public abstract class AbstractUserAction implements AdministrationAction { */ protected void verifyAccount(AuthorityProvider authorityProvider, NiFiUser user) { // load the roles for the user - Set authorities = authorityProvider.getAuthorities(user.getDn()); + Set authorities = authorityProvider.getAuthorities(user.getIdentity()); // update the user's authorities user.getAuthorities().clear(); user.getAuthorities().addAll(authorities); // get the user group - user.setUserGroup(authorityProvider.getGroupForUser(user.getDn())); + user.setUserGroup(authorityProvider.getGroupForUser(user.getIdentity())); // update the users status in case they were previously pending or disabled user.setStatus(AccountStatus.ACTIVE); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/AuthorizeUserAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/AuthorizeUserAction.java index fe32772c7c..ed4dfa1886 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/AuthorizeUserAction.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/AuthorizeUserAction.java @@ -37,11 +37,11 @@ import org.apache.nifi.user.NiFiUser; */ public class AuthorizeUserAction extends AbstractUserAction { - private final String dn; + private final String identity; private final int cacheDurationSeconds; - public AuthorizeUserAction(String dn, int cacheDurationSeconds) { - this.dn = dn; + public AuthorizeUserAction(String identity, int cacheDurationSeconds) { + this.identity = identity; this.cacheDurationSeconds = cacheDurationSeconds; } @@ -50,14 +50,14 @@ public class AuthorizeUserAction extends AbstractUserAction { UserDAO userDao = daoFactory.getUserDAO(); // get the user - NiFiUser user = userDao.findUserByDn(dn); + NiFiUser user = userDao.findUserByDn(identity); // verify the user was found if (user == null) { // determine whether this users exists boolean doesDnExist = false; try { - doesDnExist = authorityProvider.doesDnExist(dn); + doesDnExist = authorityProvider.doesDnExist(identity); } catch (AuthorityAccessException aae) { throw new AdministrationException(String.format("Unable to access authority details: %s", aae.getMessage()), aae); } @@ -66,8 +66,8 @@ public class AuthorizeUserAction extends AbstractUserAction { if (doesDnExist) { // create the user user = new NiFiUser(); - user.setDn(dn); - user.setUserName(CertificateUtils.extractUsername(dn)); + user.setIdentity(identity); + user.setUserName(CertificateUtils.extractUsername(identity)); user.setJustification("User details specified by authority provider."); try { @@ -86,12 +86,12 @@ public class AuthorizeUserAction extends AbstractUserAction { createUser.execute(daoFactory, authorityProvider); } catch (UnknownIdentityException uie) { // strange since the provider just reported this dn existed but handleing anyways... - throw new AccountNotFoundException(String.format("Unable to verify access for %s.", dn)); + throw new AccountNotFoundException(String.format("Unable to verify access for %s.", identity)); } catch (AuthorityAccessException aae) { throw new AdministrationException(String.format("Unable to access authority details: %s", aae.getMessage()), aae); } } else { - throw new AccountNotFoundException(String.format("Unable to verify access for %s.", dn)); + throw new AccountNotFoundException(String.format("Unable to verify access for %s.", identity)); } } else { Throwable providerError = null; @@ -134,7 +134,7 @@ public class AuthorizeUserAction extends AbstractUserAction { updateUserAuthorities.execute(daoFactory, authorityProvider); if (providerError != null) { - throw new AccountDisabledException(String.format("User credentials for %s were not found. This account has been disabled.", user.getDn()), providerError); + throw new AccountDisabledException(String.format("User credentials for %s were not found. This account has been disabled.", user.getIdentity()), providerError); } } @@ -165,9 +165,9 @@ public class AuthorizeUserAction extends AbstractUserAction { */ private void checkAccountStatus(NiFiUser user) { if (AccountStatus.DISABLED.equals(user.getStatus())) { - throw new AccountDisabledException(String.format("Account for %s is disabled.", user.getDn())); + throw new AccountDisabledException(String.format("The account for %s has been disabled.", user.getIdentity())); } else if (AccountStatus.PENDING.equals(user.getStatus())) { - throw new AccountPendingException(String.format("Account for %s is pending.", user.getDn())); + throw new AccountPendingException(String.format("The account for %s is currently pending approval.", user.getIdentity())); } } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DisableUserAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DisableUserAction.java index c31f107912..9e9b798dbf 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DisableUserAction.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/DisableUserAction.java @@ -63,12 +63,12 @@ public class DisableUserAction implements AdministrationAction { try { // revoke the user in the authority provider - authorityProvider.revokeUser(user.getDn()); + authorityProvider.revokeUser(user.getIdentity()); } catch (UnknownIdentityException uie) { // user identity is not known - logger.info(String.format("User %s has already been removed from the authority provider.", user.getDn())); + logger.info(String.format("User %s has already been removed from the authority provider.", user.getIdentity())); } catch (AuthorityAccessException aae) { - throw new AdministrationException(String.format("Unable to revoke user '%s': %s", user.getDn(), aae.getMessage()), aae); + throw new AdministrationException(String.format("Unable to revoke user '%s': %s", user.getIdentity(), aae.getMessage()), aae); } return user; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java new file mode 100644 index 0000000000..8763b9d0e3 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdAction.java @@ -0,0 +1,42 @@ +/* + * 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 org.apache.nifi.admin.dao.DAOFactory; +import org.apache.nifi.authorization.AuthorityProvider; + +import org.apache.nifi.admin.dao.KeyDAO; +import org.apache.nifi.key.Key; + +/** + * Gets a key for the specified key id. + */ +public class GetKeyByIdAction implements AdministrationAction { + + private final int id; + + public GetKeyByIdAction(int id) { + this.id = id; + } + + @Override + public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) { + final KeyDAO keyDao = daoFactory.getKeyDAO(); + return keyDao.findKeyById(id); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java new file mode 100644 index 0000000000..9bcb0b3f0e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetKeyByIdentityAction.java @@ -0,0 +1,42 @@ +/* + * 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 org.apache.nifi.admin.dao.DAOFactory; +import org.apache.nifi.authorization.AuthorityProvider; + +import org.apache.nifi.admin.dao.KeyDAO; +import org.apache.nifi.key.Key; + +/** + * Gets a key for the specified key id. + */ +public class GetKeyByIdentityAction implements AdministrationAction { + + private final String identity; + + public GetKeyByIdentityAction(String identity) { + this.identity = identity; + } + + @Override + public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) { + final KeyDAO keyDao = daoFactory.getKeyDAO(); + return keyDao.findLatestKeyByIdentity(identity); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java new file mode 100644 index 0000000000..bb85b6fe26 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/GetOrCreateKeyAction.java @@ -0,0 +1,48 @@ +/* + * 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 org.apache.nifi.admin.dao.DAOFactory; +import org.apache.nifi.authorization.AuthorityProvider; + +import org.apache.nifi.admin.dao.KeyDAO; +import org.apache.nifi.key.Key; + +/** + * Gets a key for the specified user identity. + */ +public class GetOrCreateKeyAction implements AdministrationAction { + + private final String identity; + + public GetOrCreateKeyAction(String identity) { + this.identity = identity; + } + + @Override + public Key execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) { + final KeyDAO keyDao = daoFactory.getKeyDAO(); + + Key key = keyDao.findLatestKeyByIdentity(identity); + if (key == null) { + key = keyDao.createKey(identity); + } + + return key; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/RequestUserAccountAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/RequestUserAccountAction.java index 3dce6d9e2e..198a32dc7c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/RequestUserAccountAction.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/RequestUserAccountAction.java @@ -30,11 +30,11 @@ import org.apache.nifi.user.NiFiUser; */ public class RequestUserAccountAction implements AdministrationAction { - private final String dn; + private final String identity; private final String justification; - public RequestUserAccountAction(String dn, String justification) { - this.dn = dn; + public RequestUserAccountAction(String identity, String justification) { + this.identity = identity; this.justification = justification; } @@ -43,15 +43,15 @@ public class RequestUserAccountAction implements AdministrationAction UserDAO userDao = daoFactory.getUserDAO(); // determine if this user already exists - NiFiUser user = userDao.findUserByDn(dn); + NiFiUser user = userDao.findUserByDn(identity); if (user != null) { - throw new IllegalArgumentException(String.format("User account for %s already exists.", dn)); + throw new IllegalArgumentException(String.format("User account for %s already exists.", identity)); } // create the user user = new NiFiUser(); - user.setDn(dn); - user.setUserName(CertificateUtils.extractUsername(dn)); + user.setIdentity(identity); + user.setUserName(CertificateUtils.extractUsername(identity)); user.setJustification(justification); user.setStatus(AccountStatus.PENDING); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/SeedUserAccountsAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/SeedUserAccountsAction.java index 6665ac09ac..c16cc712f2 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/SeedUserAccountsAction.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/SeedUserAccountsAction.java @@ -46,7 +46,7 @@ public class SeedUserAccountsAction extends AbstractUserAction { @Override public Void execute(DAOFactory daoFactory, AuthorityProvider authorityProvider) throws DataAccessException { UserDAO userDao = daoFactory.getUserDAO(); - Set authorizedDns = new HashSet<>(); + Set authorizedIdentities = new HashSet<>(); // get the current user cache final Set existingUsers; @@ -62,7 +62,7 @@ public class SeedUserAccountsAction extends AbstractUserAction { try { // all users for all roles for (final Authority authority : Authority.values()) { - authorizedDns.addAll(authorityProvider.getUsers(authority)); + authorizedIdentities.addAll(authorityProvider.getUsers(authority)); } } catch (AuthorityAccessException aae) { // unable to access the authority provider... honor the cache @@ -73,25 +73,25 @@ public class SeedUserAccountsAction extends AbstractUserAction { final Set accountsToRevoke = new HashSet<>(existingUsers); // persist the users - for (String dn : authorizedDns) { + for (String identity : authorizedIdentities) { NiFiUser user = null; try { // locate the user for this dn - user = userDao.findUserByDn(dn); + user = userDao.findUserByDn(identity); boolean newAccount = false; // if the user does not exist, create a new account if (user == null) { - logger.info(String.format("Creating user account: %s", dn)); + logger.info(String.format("Creating user account: %s", identity)); newAccount = true; // create the user user = new NiFiUser(); - user.setDn(dn); - user.setUserName(CertificateUtils.extractUsername(dn)); + user.setIdentity(identity); + user.setUserName(CertificateUtils.extractUsername(identity)); user.setJustification("User details specified by authority provider."); } else { - logger.info(String.format("User account already created: %s. Updating authorities...", dn)); + logger.info(String.format("User account already created: %s. Updating authorities...", identity)); } // verify the account @@ -142,7 +142,7 @@ public class SeedUserAccountsAction extends AbstractUserAction { } try { - logger.info(String.format("User not authorized with configured provider: %s. Disabling account...", user.getDn())); + logger.info(String.format("User not authorized with configured provider: %s. Disabling account...", user.getIdentity())); // disable the account and reset its last verified timestamp since it was not found // in the current configured authority provider diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UngroupUserAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UngroupUserAction.java index 01eaf5f1c7..2604a47ca6 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UngroupUserAction.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UngroupUserAction.java @@ -56,11 +56,11 @@ public class UngroupUserAction extends AbstractUserAction { try { // update the authority provider - authorityProvider.ungroupUser(user.getDn()); + authorityProvider.ungroupUser(user.getIdentity()); } catch (UnknownIdentityException uie) { - throw new AccountNotFoundException(String.format("Unable to ungroup user '%s': %s", user.getDn(), uie.getMessage()), uie); + throw new AccountNotFoundException(String.format("Unable to ungroup user '%s': %s", user.getIdentity(), uie.getMessage()), uie); } catch (AuthorityAccessException aae) { - throw new AdministrationException(String.format("Unable to ungroup user '%s': %s", user.getDn(), aae.getMessage()), aae); + throw new AdministrationException(String.format("Unable to ungroup user '%s': %s", user.getIdentity(), aae.getMessage()), aae); } return null; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UpdateUserAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UpdateUserAction.java index cef21d7d97..ecb91e602c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UpdateUserAction.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UpdateUserAction.java @@ -61,41 +61,41 @@ public class UpdateUserAction extends AbstractUserAction { } // determine whether this users exists - boolean doesDnExist = false; + boolean doesIdentityExist = false; try { - doesDnExist = authorityProvider.doesDnExist(user.getDn()); + doesIdentityExist = authorityProvider.doesDnExist(user.getIdentity()); } catch (AuthorityAccessException aae) { throw new AdministrationException(String.format("Unable to access authority details: %s", aae.getMessage()), aae); } // if the user already doesn't exist, add them - if (!doesDnExist) { + if (!doesIdentityExist) { try { // add the account account and group if necessary - authorityProvider.addUser(user.getDn(), user.getUserGroup()); + authorityProvider.addUser(user.getIdentity(), user.getUserGroup()); } catch (final IdentityAlreadyExistsException iaee) { - logger.warn(String.format("User '%s' already exists in the authority provider. Continuing with user update.", user.getDn())); + logger.warn(String.format("User '%s' already exists in the authority provider. Continuing with user update.", user.getIdentity())); } catch (AuthorityAccessException aae) { - throw new AdministrationException(String.format("Unable to access authorities for '%s': %s", user.getDn(), aae.getMessage()), aae); + throw new AdministrationException(String.format("Unable to access authorities for '%s': %s", user.getIdentity(), aae.getMessage()), aae); } } try { // update the authority provider as approprivate - authorityProvider.setAuthorities(user.getDn(), authorities); + authorityProvider.setAuthorities(user.getIdentity(), authorities); } catch (UnknownIdentityException uie) { - throw new AccountNotFoundException(String.format("Unable to modify authorities for '%s': %s.", user.getDn(), uie.getMessage()), uie); + throw new AccountNotFoundException(String.format("Unable to modify authorities for '%s': %s.", user.getIdentity(), uie.getMessage()), uie); } catch (AuthorityAccessException aae) { - throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getDn(), aae.getMessage()), aae); + throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getIdentity(), aae.getMessage()), aae); } try { // get the user group - user.setUserGroup(authorityProvider.getGroupForUser(user.getDn())); + user.setUserGroup(authorityProvider.getGroupForUser(user.getIdentity())); } catch (UnknownIdentityException uie) { - throw new AccountNotFoundException(String.format("Unable to determine the group for '%s': %s.", user.getDn(), uie.getMessage()), uie); + throw new AccountNotFoundException(String.format("Unable to determine the group for '%s': %s.", user.getIdentity(), uie.getMessage()), uie); } catch (AuthorityAccessException aae) { - throw new AdministrationException(String.format("Unable to access the group for '%s': %s.", user.getDn(), aae.getMessage()), aae); + throw new AdministrationException(String.format("Unable to access the group for '%s': %s.", user.getIdentity(), aae.getMessage()), aae); } // since all the authorities were updated accordingly, set the authorities diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UpdateUserGroupAction.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UpdateUserGroupAction.java index 56b214cd95..1d7941f9ba 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UpdateUserGroupAction.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/action/UpdateUserGroupAction.java @@ -61,7 +61,7 @@ public class UpdateUserGroupAction extends AbstractUserAction { // record the new users being added to this group final Set newUsers = new HashSet<>(); - final Set newUserDns = new HashSet<>(); + final Set newUserIdentities = new HashSet<>(); // if the user ids have been specified we need to create/update a group using the specified group name if (userIds != null) { @@ -81,13 +81,13 @@ public class UpdateUserGroupAction extends AbstractUserAction { try { // if the user is unknown to the authority provider we cannot continue - if (!authorityProvider.doesDnExist(user.getDn()) || AccountStatus.DISABLED.equals(user.getStatus())) { - throw new IllegalStateException(String.format("Unable to group these users because access for '%s' is not %s.", user.getDn(), AccountStatus.ACTIVE.toString())); + if (!authorityProvider.doesDnExist(user.getIdentity()) || AccountStatus.DISABLED.equals(user.getStatus())) { + throw new IllegalStateException(String.format("Unable to group these users because access for '%s' is not %s.", user.getIdentity(), AccountStatus.ACTIVE.toString())); } // record the user being added to this group newUsers.add(user); - newUserDns.add(user.getDn()); + newUserIdentities.add(user.getIdentity()); } catch (final AuthorityAccessException aae) { throw new AdministrationException(String.format("Unable to access authority details: %s", aae.getMessage()), aae); } @@ -95,11 +95,11 @@ public class UpdateUserGroupAction extends AbstractUserAction { try { // update the authority provider - authorityProvider.setUsersGroup(newUserDns, group); + authorityProvider.setUsersGroup(newUserIdentities, group); } catch (UnknownIdentityException uie) { - throw new AccountNotFoundException(String.format("Unable to set user group '%s': %s", StringUtils.join(newUserDns, ", "), uie.getMessage()), uie); + throw new AccountNotFoundException(String.format("Unable to set user group '%s': %s", StringUtils.join(newUserIdentities, ", "), uie.getMessage()), uie); } catch (AuthorityAccessException aae) { - throw new AdministrationException(String.format("Unable to set user group '%s': %s", StringUtils.join(newUserDns, ", "), aae.getMessage()), aae); + throw new AdministrationException(String.format("Unable to set user group '%s': %s", StringUtils.join(newUserIdentities, ", "), aae.getMessage()), aae); } } @@ -118,35 +118,35 @@ public class UpdateUserGroupAction extends AbstractUserAction { if (authorities != null) { try { // update the authority provider as approprivate - authorityProvider.setAuthorities(user.getDn(), authorities); + authorityProvider.setAuthorities(user.getIdentity(), authorities); // since all the authorities were updated accordingly, set the authorities user.getAuthorities().clear(); user.getAuthorities().addAll(authorities); } catch (UnknownIdentityException uie) { - throw new AccountNotFoundException(String.format("Unable to modify authorities for '%s': %s.", user.getDn(), uie.getMessage()), uie); + throw new AccountNotFoundException(String.format("Unable to modify authorities for '%s': %s.", user.getIdentity(), uie.getMessage()), uie); } catch (AuthorityAccessException aae) { - throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getDn(), aae.getMessage()), aae); + throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getIdentity(), aae.getMessage()), aae); } } else { try { // refresh the authorities according to the provider user.getAuthorities().clear(); - user.getAuthorities().addAll(authorityProvider.getAuthorities(user.getDn())); + user.getAuthorities().addAll(authorityProvider.getAuthorities(user.getIdentity())); } catch (UnknownIdentityException uie) { - throw new AccountNotFoundException(String.format("Unable to determine the authorities for '%s': %s.", user.getDn(), uie.getMessage()), uie); + throw new AccountNotFoundException(String.format("Unable to determine the authorities for '%s': %s.", user.getIdentity(), uie.getMessage()), uie); } catch (AuthorityAccessException aae) { - throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getDn(), aae.getMessage()), aae); + throw new AdministrationException(String.format("Unable to access authorities for '%s': %s.", user.getIdentity(), aae.getMessage()), aae); } } try { // get the user group - user.setUserGroup(authorityProvider.getGroupForUser(user.getDn())); + user.setUserGroup(authorityProvider.getGroupForUser(user.getIdentity())); } catch (UnknownIdentityException uie) { - throw new AccountNotFoundException(String.format("Unable to determine the group for '%s': %s.", user.getDn(), uie.getMessage()), uie); + throw new AccountNotFoundException(String.format("Unable to determine the group for '%s': %s.", user.getIdentity(), uie.getMessage()), uie); } catch (AuthorityAccessException aae) { - throw new AdministrationException(String.format("Unable to access the group for '%s': %s.", user.getDn(), aae.getMessage()), aae); + throw new AdministrationException(String.format("Unable to access the group for '%s': %s.", user.getIdentity(), aae.getMessage()), aae); } // update the users status in case they were previously pending or disabled diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java new file mode 100644 index 0000000000..ca0a1247f1 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/admin/service/impl/StandardKeyService.java @@ -0,0 +1,126 @@ +/* + * 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.apache.nifi.admin.dao.DataAccessException; +import org.apache.nifi.admin.service.AdministrationException; +import org.apache.nifi.admin.service.KeyService; +import org.apache.nifi.admin.service.action.GetKeyByIdAction; +import org.apache.nifi.admin.service.action.GetOrCreateKeyAction; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.apache.nifi.key.Key; + +/** + * + */ +public class StandardKeyService implements KeyService { + + private static final Logger logger = LoggerFactory.getLogger(StandardKeyService.class); + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); + private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); + + private TransactionBuilder transactionBuilder; + + @Override + public Key getKey(int id) { + Transaction transaction = null; + Key key = null; + + readLock.lock(); + try { + // start the transaction + transaction = transactionBuilder.start(); + + // get the key + GetKeyByIdAction addActions = new GetKeyByIdAction(id); + key = transaction.execute(addActions); + + // commit the transaction + transaction.commit(); + } catch (TransactionException | DataAccessException te) { + rollback(transaction); + throw new AdministrationException(te); + } catch (Throwable t) { + rollback(transaction); + throw t; + } finally { + closeQuietly(transaction); + readLock.unlock(); + } + + return key; + } + + @Override + public Key getOrCreateKey(String identity) { + Transaction transaction = null; + Key key = null; + + writeLock.lock(); + try { + // start the transaction + transaction = transactionBuilder.start(); + + // seed the accounts + GetOrCreateKeyAction addActions = new GetOrCreateKeyAction(identity); + key = transaction.execute(addActions); + + // commit the transaction + transaction.commit(); + } catch (TransactionException | DataAccessException te) { + rollback(transaction); + throw new AdministrationException(te); + } catch (Throwable t) { + rollback(transaction); + throw t; + } finally { + closeQuietly(transaction); + writeLock.unlock(); + } + + return key; + } + + private void rollback(Transaction transaction) { + if (transaction != null) { + transaction.rollback(); + } + } + + private void closeQuietly(final Transaction transaction) { + if (transaction != null) { + try { + transaction.close(); + } catch (final IOException ioe) { + } + } + } + + public void setTransactionBuilder(TransactionBuilder transactionBuilder) { + this.transactionBuilder = transactionBuilder; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java new file mode 100644 index 0000000000..9ce7a9ae31 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/key/Key.java @@ -0,0 +1,69 @@ +/* + * 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.key; + +import java.io.Serializable; + +/** + * An signing key for a NiFi user. + */ +public class Key implements Serializable { + + private int id; + private String identity; + private String key; + + /** + * The key id. + * + * @return the id + */ + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + /** + * The identity of the user this key is associated with. + * + * @return the identity + */ + public String getIdentity() { + return identity; + } + + public void setIdentity(String identity) { + this.identity = identity; + } + + /** + * The signing key. + * + * @return the signing key + */ + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/user/NiFiUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/user/NiFiUser.java index a47bde9b8c..231b13366e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/user/NiFiUser.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/java/org/apache/nifi/user/NiFiUser.java @@ -29,10 +29,10 @@ import org.apache.commons.lang3.StringUtils; */ public class NiFiUser implements Serializable { - public static final String ANONYMOUS_USER_DN = "anonymous"; + public static final String ANONYMOUS_USER_IDENTITY = "anonymous"; private String id; - private String dn; + private String identity; private String userName; private String userGroup; private String justification; @@ -55,12 +55,12 @@ public class NiFiUser implements Serializable { this.creation = creation; } - public String getDn() { - return dn; + public String getIdentity() { + return identity; } - public void setDn(String dn) { - this.dn = dn; + public void setIdentity(String identity) { + this.identity = identity; } public String getUserName() { @@ -143,7 +143,7 @@ public class NiFiUser implements Serializable { return false; } final NiFiUser other = (NiFiUser) obj; - if (!Objects.equals(this.dn, other.dn)) { + if (!Objects.equals(this.identity, other.identity)) { return false; } return true; @@ -152,13 +152,13 @@ public class NiFiUser implements Serializable { @Override public int hashCode() { int hash = 7; - hash = 53 * hash + Objects.hashCode(this.dn); + hash = 53 * hash + Objects.hashCode(this.identity); return hash; } @Override public String toString() { - return String.format("dn[%s], userName[%s], justification[%s], authorities[%s]", getDn(), getUserName(), getJustification(), StringUtils.join(getAuthorities(), ", ")); + return String.format("identity[%s], userName[%s], justification[%s], authorities[%s]", getIdentity(), getUserName(), getJustification(), StringUtils.join(getAuthorities(), ", ")); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/resources/nifi-administration-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/resources/nifi-administration-context.xml index 8cb4b97d57..6d7b73964c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/resources/nifi-administration-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/main/resources/nifi-administration-context.xml @@ -36,6 +36,11 @@ + + + + + @@ -48,6 +53,12 @@ + + + + + + @@ -59,4 +70,9 @@ + + + + + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/AuthorizeUserActionTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/AuthorizeUserActionTest.java index 28ea4a943b..8d3c15a7e9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/AuthorizeUserActionTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/AuthorizeUserActionTest.java @@ -54,17 +54,17 @@ public class AuthorizeUserActionTest { private static final String USER_ID_10 = "10"; private static final String USER_ID_11 = "11"; - private static final String USER_DN_1 = "authority access exception while searching for user"; - private static final String USER_DN_2 = "unknown user"; - private static final String USER_DN_3 = "user removed after checking existence"; - private static final String USER_DN_4 = "access exception getting authorities"; - private static final String USER_DN_5 = "error creating user account"; - private static final String USER_DN_6 = "create user general sequence"; - private static final String USER_DN_7 = "existing user requires verification"; - private static final String USER_DN_8 = "existing user does not require verification"; - private static final String USER_DN_9 = "existing pending user"; - private static final String USER_DN_10 = "existing disabled user"; - private static final String USER_DN_11 = "existing user is now unknown in the authority provider"; + private static final String USER_IDENTITY_1 = "authority access exception while searching for user"; + private static final String USER_IDENTITY_2 = "unknown user"; + private static final String USER_IDENTITY_3 = "user removed after checking existence"; + private static final String USER_IDENTITY_4 = "access exception getting authorities"; + private static final String USER_IDENTITY_5 = "error creating user account"; + private static final String USER_IDENTITY_6 = "create user general sequence"; + private static final String USER_IDENTITY_7 = "existing user requires verification"; + private static final String USER_IDENTITY_8 = "existing user does not require verification"; + private static final String USER_IDENTITY_9 = "existing pending user"; + private static final String USER_IDENTITY_10 = "existing disabled user"; + private static final String USER_IDENTITY_11 = "existing user is now unknown in the authority provider"; private DAOFactory daoFactory; private UserDAO userDao; @@ -85,18 +85,18 @@ public class AuthorizeUserActionTest { if (USER_ID_7.equals(id)) { user = new NiFiUser(); user.setId(USER_ID_7); - user.setDn(USER_DN_7); + user.setIdentity(USER_IDENTITY_7); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); } else if (USER_ID_8.equals(id)) { user = new NiFiUser(); user.setId(USER_ID_8); - user.setDn(USER_DN_8); + user.setIdentity(USER_IDENTITY_8); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); user.setLastVerified(new Date()); } else if (USER_ID_11.equals(id)) { user = new NiFiUser(); user.setId(USER_ID_11); - user.setDn(USER_DN_11); + user.setIdentity(USER_IDENTITY_11); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); user.setStatus(AccountStatus.ACTIVE); } @@ -112,35 +112,35 @@ public class AuthorizeUserActionTest { NiFiUser user = null; switch (dn) { - case USER_DN_7: + case USER_IDENTITY_7: user = new NiFiUser(); user.setId(USER_ID_7); - user.setDn(USER_DN_7); + user.setIdentity(USER_IDENTITY_7); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); break; - case USER_DN_8: + case USER_IDENTITY_8: user = new NiFiUser(); user.setId(USER_ID_8); - user.setDn(USER_DN_8); + user.setIdentity(USER_IDENTITY_8); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); user.setLastVerified(new Date()); break; - case USER_DN_9: + case USER_IDENTITY_9: user = new NiFiUser(); user.setId(USER_ID_9); - user.setDn(USER_DN_9); + user.setIdentity(USER_IDENTITY_9); user.setStatus(AccountStatus.PENDING); break; - case USER_DN_10: + case USER_IDENTITY_10: user = new NiFiUser(); user.setId(USER_ID_10); - user.setDn(USER_DN_10); + user.setIdentity(USER_IDENTITY_10); user.setStatus(AccountStatus.DISABLED); break; - case USER_DN_11: + case USER_IDENTITY_11: user = new NiFiUser(); user.setId(USER_ID_11); - user.setDn(USER_DN_11); + user.setIdentity(USER_IDENTITY_11); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); user.setStatus(AccountStatus.ACTIVE); break; @@ -154,10 +154,10 @@ public class AuthorizeUserActionTest { public Void answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); NiFiUser user = (NiFiUser) args[0]; - switch (user.getDn()) { - case USER_DN_5: + switch (user.getIdentity()) { + case USER_IDENTITY_5: throw new DataAccessException(); - case USER_DN_6: + case USER_IDENTITY_6: user.setId(USER_ID_6); break; } @@ -215,9 +215,9 @@ public class AuthorizeUserActionTest { Object[] args = invocation.getArguments(); String dn = (String) args[0]; switch (dn) { - case USER_DN_1: + case USER_IDENTITY_1: throw new AuthorityAccessException(StringUtils.EMPTY); - case USER_DN_2: + case USER_IDENTITY_2: return false; } @@ -231,21 +231,21 @@ public class AuthorizeUserActionTest { String dn = (String) args[0]; Set authorities = EnumSet.noneOf(Authority.class); switch (dn) { - case USER_DN_3: + case USER_IDENTITY_3: throw new UnknownIdentityException(StringUtils.EMPTY); - case USER_DN_4: + case USER_IDENTITY_4: throw new AuthorityAccessException(StringUtils.EMPTY); - case USER_DN_6: + case USER_IDENTITY_6: authorities.add(Authority.ROLE_MONITOR); break; - case USER_DN_7: + case USER_IDENTITY_7: authorities.add(Authority.ROLE_DFM); break; - case USER_DN_9: + case USER_IDENTITY_9: throw new UnknownIdentityException(StringUtils.EMPTY); - case USER_DN_10: + case USER_IDENTITY_10: throw new UnknownIdentityException(StringUtils.EMPTY); - case USER_DN_11: + case USER_IDENTITY_11: throw new UnknownIdentityException(StringUtils.EMPTY); } @@ -272,7 +272,7 @@ public class AuthorizeUserActionTest { */ @Test(expected = AdministrationException.class) public void testAuthorityAccessExceptionInDoesDnExist() throws Exception { - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_1, 0); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_1, 0); authorizeUser.execute(daoFactory, authorityProvider); } @@ -283,7 +283,7 @@ public class AuthorizeUserActionTest { */ @Test(expected = AccountNotFoundException.class) public void testUnknownUser() throws Exception { - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_2, 0); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_2, 0); authorizeUser.execute(daoFactory, authorityProvider); } @@ -294,7 +294,7 @@ public class AuthorizeUserActionTest { */ @Test(expected = AccountNotFoundException.class) public void testUserRemovedAfterCheckingExistence() throws Exception { - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_3, 0); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_3, 0); authorizeUser.execute(daoFactory, authorityProvider); } @@ -305,7 +305,7 @@ public class AuthorizeUserActionTest { */ @Test(expected = AdministrationException.class) public void testAuthorityAccessException() throws Exception { - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_4, 0); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_4, 0); authorizeUser.execute(daoFactory, authorityProvider); } @@ -316,7 +316,7 @@ public class AuthorizeUserActionTest { */ @Test(expected = DataAccessException.class) public void testErrorCreatingUserAccount() throws Exception { - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_5, 0); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_5, 0); authorizeUser.execute(daoFactory, authorityProvider); } @@ -327,11 +327,11 @@ public class AuthorizeUserActionTest { */ @Test public void testAccountCreation() throws Exception { - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_6, 0); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_6, 0); NiFiUser user = authorizeUser.execute(daoFactory, authorityProvider); // verify the user - Assert.assertEquals(USER_DN_6, user.getDn()); + Assert.assertEquals(USER_IDENTITY_6, user.getIdentity()); Assert.assertEquals(1, user.getAuthorities().size()); Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_MONITOR)); @@ -347,11 +347,11 @@ public class AuthorizeUserActionTest { */ @Test public void testExistingUserRequiresVerification() throws Exception { - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_7, 0); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_7, 0); NiFiUser user = authorizeUser.execute(daoFactory, authorityProvider); // verify the user - Assert.assertEquals(USER_DN_7, user.getDn()); + Assert.assertEquals(USER_IDENTITY_7, user.getIdentity()); Assert.assertEquals(1, user.getAuthorities().size()); Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_DFM)); @@ -369,11 +369,11 @@ public class AuthorizeUserActionTest { @Test public void testExistingUserNoVerification() throws Exception { // disabling verification by passing in a large cache duration - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_8, Integer.MAX_VALUE); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_8, Integer.MAX_VALUE); NiFiUser user = authorizeUser.execute(daoFactory, authorityProvider); // verify the user - Assert.assertEquals(USER_DN_8, user.getDn()); + Assert.assertEquals(USER_IDENTITY_8, user.getIdentity()); Assert.assertEquals(1, user.getAuthorities().size()); Assert.assertTrue(user.getAuthorities().contains(Authority.ROLE_MONITOR)); @@ -391,7 +391,7 @@ public class AuthorizeUserActionTest { @Test(expected = AccountPendingException.class) public void testExistingPendingUser() throws Exception { // disabling verification by passing in a large cache duration - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_9, Integer.MAX_VALUE); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_9, Integer.MAX_VALUE); authorizeUser.execute(daoFactory, authorityProvider); } @@ -403,7 +403,7 @@ public class AuthorizeUserActionTest { @Test(expected = AccountDisabledException.class) public void testExistingDisabledUser() throws Exception { // disabling verification by passing in a large cache duration - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_10, Integer.MAX_VALUE); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_10, Integer.MAX_VALUE); authorizeUser.execute(daoFactory, authorityProvider); } @@ -416,7 +416,7 @@ public class AuthorizeUserActionTest { @Test public void testExistingActiveUserNotFoundInProvider() throws Exception { try { - AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_DN_11, 0); + AuthorizeUserAction authorizeUser = new AuthorizeUserAction(USER_IDENTITY_11, 0); authorizeUser.execute(daoFactory, authorityProvider); Assert.fail(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/CreateUserActionTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/CreateUserActionTest.java index 6486d32924..e372781867 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/CreateUserActionTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/CreateUserActionTest.java @@ -40,8 +40,8 @@ public class CreateUserActionTest { private final String USER_ID_2 = "2"; private final String USER_ID_3 = "3"; - private final String USER_DN_1 = "data access exception when creating user"; - private final String USER_DN_3 = "general create user case"; + private final String USER_IDENTITY_1 = "data access exception when creating user"; + private final String USER_IDENTITY_3 = "general create user case"; private DAOFactory daoFactory; private UserDAO userDao; @@ -57,9 +57,9 @@ public class CreateUserActionTest { Object[] args = invocation.getArguments(); NiFiUser user = (NiFiUser) args[0]; - if (USER_DN_1.equals(user.getDn())) { + if (USER_IDENTITY_1.equals(user.getIdentity())) { throw new DataAccessException(); - } else if (USER_DN_3.equals(user.getDn())) { + } else if (USER_IDENTITY_3.equals(user.getIdentity())) { user.setId(USER_ID_3); } @@ -100,7 +100,7 @@ public class CreateUserActionTest { @Test(expected = DataAccessException.class) public void testExceptionCreatingUser() throws Exception { NiFiUser user = new NiFiUser(); - user.setDn(USER_DN_1); + user.setIdentity(USER_IDENTITY_1); CreateUserAction createUser = new CreateUserAction(user); createUser.execute(daoFactory, null); @@ -128,7 +128,7 @@ public class CreateUserActionTest { @Test public void testCreateUserAccount() throws Exception { NiFiUser user = new NiFiUser(); - user.setDn(USER_DN_3); + user.setIdentity(USER_IDENTITY_3); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_DFM, Authority.ROLE_ADMIN)); CreateUserAction createUser = new CreateUserAction(user); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/DisableUserActionTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/DisableUserActionTest.java index b0e1ac10f6..ac2ab29103 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/DisableUserActionTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/DisableUserActionTest.java @@ -40,8 +40,8 @@ public class DisableUserActionTest { private static final String USER_ID_3 = "3"; private static final String USER_ID_4 = "4"; - private static final String USER_DN_3 = "authority access exception"; - private static final String USER_DN_4 = "general disable user case"; + private static final String USER_IDENTITY_3 = "authority access exception"; + private static final String USER_IDENTITY_4 = "general disable user case"; private DAOFactory daoFactory; private UserDAO userDao; @@ -66,11 +66,11 @@ public class DisableUserActionTest { } else if (USER_ID_3.equals(id)) { user = new NiFiUser(); user.setId(id); - user.setDn(USER_DN_3); + user.setIdentity(USER_IDENTITY_3); } else if (USER_ID_4.equals(id)) { user = new NiFiUser(); user.setId(id); - user.setDn(USER_DN_4); + user.setIdentity(USER_IDENTITY_4); user.setStatus(AccountStatus.ACTIVE); } return user; @@ -103,7 +103,7 @@ public class DisableUserActionTest { Object[] args = invocation.getArguments(); String dn = (String) args[0]; - if (USER_DN_3.equals(dn)) { + if (USER_IDENTITY_3.equals(dn)) { throw new AuthorityAccessException(StringUtils.EMPTY); } @@ -158,11 +158,11 @@ public class DisableUserActionTest { // verify the user Assert.assertEquals(USER_ID_4, user.getId()); - Assert.assertEquals(USER_DN_4, user.getDn()); + Assert.assertEquals(USER_IDENTITY_4, user.getIdentity()); Assert.assertEquals(AccountStatus.DISABLED, user.getStatus()); // verify the interaction with the dao and provider Mockito.verify(userDao, Mockito.times(1)).updateUser(user); - Mockito.verify(authorityProvider, Mockito.times(1)).revokeUser(USER_DN_4); + Mockito.verify(authorityProvider, Mockito.times(1)).revokeUser(USER_IDENTITY_4); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/RequestUserAccountActionTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/RequestUserAccountActionTest.java index 7707b2cb44..7bc863b20f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/RequestUserAccountActionTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/RequestUserAccountActionTest.java @@ -36,9 +36,9 @@ public class RequestUserAccountActionTest { private static final String USER_ID_3 = "3"; - private static final String USER_DN_1 = "existing user account dn"; - private static final String USER_DN_2 = "data access exception"; - private static final String USER_DN_3 = "new account request"; + private static final String USER_IDENTITY_1 = "existing user account"; + private static final String USER_IDENTITY_2 = "data access exception"; + private static final String USER_IDENTITY_3 = "new account request"; private DAOFactory daoFactory; private UserDAO userDao; @@ -54,7 +54,7 @@ public class RequestUserAccountActionTest { String dn = (String) args[0]; NiFiUser user = null; - if (USER_DN_1.equals(dn)) { + if (USER_IDENTITY_1.equals(dn)) { user = new NiFiUser(); } return user; @@ -65,10 +65,10 @@ public class RequestUserAccountActionTest { public Void answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); NiFiUser user = (NiFiUser) args[0]; - switch (user.getDn()) { - case USER_DN_2: + switch (user.getIdentity()) { + case USER_IDENTITY_2: throw new DataAccessException(); - case USER_DN_3: + case USER_IDENTITY_3: user.setId(USER_ID_3); break; } @@ -90,7 +90,7 @@ public class RequestUserAccountActionTest { */ @Test(expected = IllegalArgumentException.class) public void testExistingAccount() throws Exception { - RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_DN_1, StringUtils.EMPTY); + RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_IDENTITY_1, StringUtils.EMPTY); requestUserAccount.execute(daoFactory, null); } @@ -102,7 +102,7 @@ public class RequestUserAccountActionTest { */ @Test(expected = DataAccessException.class) public void testDataAccessException() throws Exception { - RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_DN_2, StringUtils.EMPTY); + RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_IDENTITY_2, StringUtils.EMPTY); requestUserAccount.execute(daoFactory, null); } @@ -113,12 +113,12 @@ public class RequestUserAccountActionTest { */ @Test public void testRequestUserAccountAction() throws Exception { - RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_DN_3, StringUtils.EMPTY); + RequestUserAccountAction requestUserAccount = new RequestUserAccountAction(USER_IDENTITY_3, StringUtils.EMPTY); NiFiUser user = requestUserAccount.execute(daoFactory, null); // verfiy the user Assert.assertEquals(USER_ID_3, user.getId()); - Assert.assertEquals(USER_DN_3, user.getDn()); + Assert.assertEquals(USER_IDENTITY_3, user.getIdentity()); Assert.assertEquals(AccountStatus.PENDING, user.getStatus()); // verify interaction with dao diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/SeedUserAccountsActionTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/SeedUserAccountsActionTest.java index 652d99209c..58db56a08b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/SeedUserAccountsActionTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/SeedUserAccountsActionTest.java @@ -44,10 +44,10 @@ public class SeedUserAccountsActionTest { private static final String USER_ID_3 = "3"; private static final String USER_ID_4 = "4"; - private static final String USER_DN_1 = "user dn 1 - active user - remove monitor and operator, add dfm"; - private static final String USER_DN_2 = "user dn 2 - active user - no action"; - private static final String USER_DN_3 = "user dn 3 - pending user - add operator"; - private static final String USER_DN_4 = "user dn 4 - new user - add monitor"; + private static final String USER_IDENTITY_1 = "user 1 - active user - remove monitor and operator, add dfm"; + private static final String USER_IDENTITY_2 = "user 2 - active user - no action"; + private static final String USER_IDENTITY_3 = "user 3 - pending user - add operator"; + private static final String USER_IDENTITY_4 = "user 4 - new user - add monitor"; private DAOFactory daoFactory; private UserDAO userDao; @@ -68,19 +68,19 @@ public class SeedUserAccountsActionTest { if (USER_ID_1.equals(id)) { user = new NiFiUser(); user.setId(USER_ID_1); - user.setDn(USER_DN_1); + user.setIdentity(USER_IDENTITY_1); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); user.setStatus(AccountStatus.ACTIVE); } else if (USER_ID_2.equals(id)) { user = new NiFiUser(); user.setId(USER_ID_2); - user.setDn(USER_DN_2); + user.setIdentity(USER_IDENTITY_2); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_ADMIN)); user.setStatus(AccountStatus.ACTIVE); } else if (USER_ID_3.equals(id)) { user = new NiFiUser(); user.setId(USER_ID_3); - user.setDn(USER_DN_3); + user.setIdentity(USER_IDENTITY_3); user.setStatus(AccountStatus.PENDING); } return user; @@ -93,22 +93,22 @@ public class SeedUserAccountsActionTest { String dn = (String) args[0]; NiFiUser user = null; - if (USER_DN_1.equals(dn)) { + if (USER_IDENTITY_1.equals(dn)) { user = new NiFiUser(); user.setId(USER_ID_1); - user.setDn(USER_DN_1); + user.setIdentity(USER_IDENTITY_1); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); user.setStatus(AccountStatus.ACTIVE); - } else if (USER_DN_2.equals(dn)) { + } else if (USER_IDENTITY_2.equals(dn)) { user = new NiFiUser(); user.setId(USER_ID_2); - user.setDn(USER_DN_2); + user.setIdentity(USER_IDENTITY_2); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_ADMIN)); user.setStatus(AccountStatus.ACTIVE); - } else if (USER_DN_3.equals(dn)) { + } else if (USER_IDENTITY_3.equals(dn)) { user = new NiFiUser(); user.setId(USER_ID_3); - user.setDn(USER_DN_3); + user.setIdentity(USER_IDENTITY_3); user.setStatus(AccountStatus.PENDING); } return user; @@ -120,7 +120,7 @@ public class SeedUserAccountsActionTest { Object[] args = invocation.getArguments(); NiFiUser user = (NiFiUser) args[0]; - if (USER_DN_4.equals(user.getDn())) { + if (USER_IDENTITY_4.equals(user.getIdentity())) { user.setId(USER_ID_4); } @@ -141,13 +141,13 @@ public class SeedUserAccountsActionTest { Set users = new HashSet<>(); if (Authority.ROLE_DFM.equals(role)) { - users.add(USER_DN_1); + users.add(USER_IDENTITY_1); } else if (Authority.ROLE_ADMIN.equals(role)) { - users.add(USER_DN_2); + users.add(USER_IDENTITY_2); } else if (Authority.ROLE_PROXY.equals(role)) { - users.add(USER_DN_3); + users.add(USER_IDENTITY_3); } else if (Authority.ROLE_MONITOR.equals(role)) { - users.add(USER_DN_4); + users.add(USER_IDENTITY_4); } return users; } @@ -160,16 +160,16 @@ public class SeedUserAccountsActionTest { Set authorities = EnumSet.noneOf(Authority.class); switch (dn) { - case USER_DN_1: + case USER_IDENTITY_1: authorities.add(Authority.ROLE_DFM); break; - case USER_DN_2: + case USER_IDENTITY_2: authorities.add(Authority.ROLE_ADMIN); break; - case USER_DN_3: + case USER_IDENTITY_3: authorities.add(Authority.ROLE_PROXY); break; - case USER_DN_4: + case USER_IDENTITY_4: authorities.add(Authority.ROLE_MONITOR); break; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/SetUserAuthoritiesActionTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/SetUserAuthoritiesActionTest.java index 22504f7b1f..5effdbba8a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/SetUserAuthoritiesActionTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-administration/src/test/java/org/apache/nifi/admin/service/action/SetUserAuthoritiesActionTest.java @@ -46,8 +46,8 @@ public class SetUserAuthoritiesActionTest { private static final String USER_ID_2 = "2"; private static final String USER_ID_3 = "3"; - private static final String USER_DN_2 = "user dn 2"; - private static final String USER_DN_3 = "user dn 3"; + private static final String USER_IDENTITY_2 = "user 2"; + private static final String USER_IDENTITY_3 = "user 3"; private DAOFactory daoFactory; private UserDAO userDao; @@ -70,11 +70,11 @@ public class SetUserAuthoritiesActionTest { } else if (USER_ID_2.equals(id)) { user = new NiFiUser(); user.setId(USER_ID_2); - user.setDn(USER_DN_2); + user.setIdentity(USER_IDENTITY_2); } else if (USER_ID_3.equals(id)) { user = new NiFiUser(); user.setId(USER_ID_3); - user.setDn(USER_DN_3); + user.setIdentity(USER_IDENTITY_3); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); user.setStatus(AccountStatus.ACTIVE); } @@ -88,10 +88,10 @@ public class SetUserAuthoritiesActionTest { String dn = (String) args[0]; NiFiUser user = null; - if (USER_DN_3.equals(dn)) { + if (USER_IDENTITY_3.equals(dn)) { user = new NiFiUser(); user.setId(USER_ID_3); - user.setDn(USER_DN_3); + user.setIdentity(USER_IDENTITY_3); user.getAuthorities().addAll(EnumSet.of(Authority.ROLE_MONITOR)); user.setStatus(AccountStatus.ACTIVE); } @@ -148,7 +148,7 @@ public class SetUserAuthoritiesActionTest { String dn = (String) args[0]; Set authorities = EnumSet.noneOf(Authority.class); - if (USER_DN_3.equals(dn)) { + if (USER_IDENTITY_3.equals(dn)) { authorities.add(Authority.ROLE_DFM); } @@ -162,7 +162,7 @@ public class SetUserAuthoritiesActionTest { String dn = (String) args[0]; Set authorites = (Set) args[1]; - if (USER_DN_2.equals(dn)) { + if (USER_IDENTITY_2.equals(dn)) { throw new AuthorityAccessException(StringUtils.EMPTY); } @@ -218,6 +218,6 @@ public class SetUserAuthoritiesActionTest { Set authoritiesAddedToProvider = EnumSet.of(Authority.ROLE_ADMIN); // verify interaction with provider - Mockito.verify(authorityProvider, Mockito.times(1)).setAuthorities(USER_DN_3, authoritiesAddedToProvider); + Mockito.verify(authorityProvider, Mockito.times(1)).setAuthorities(USER_IDENTITY_3, authoritiesAddedToProvider); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessConfigurationDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessConfigurationDTO.java new file mode 100644 index 0000000000..d9719b3f80 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessConfigurationDTO.java @@ -0,0 +1,61 @@ +/* + * 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.api.dto; + +import com.wordnik.swagger.annotations.ApiModelProperty; +import javax.xml.bind.annotation.XmlType; + +/** + * Details for the access configuration. + */ +@XmlType(name = "accessConfig") +public class AccessConfigurationDTO { + + private Boolean supportsLogin; + private Boolean supportsAnonymous; + + /** + * @return Indicates whether or not this NiFi supports user login. + */ + @ApiModelProperty( + value = "Indicates whether or not this NiFi supports user login.", + readOnly = true + ) + public Boolean getSupportsLogin() { + return supportsLogin; + } + + public void setSupportsLogin(Boolean supportsLogin) { + this.supportsLogin = supportsLogin; + } + + /** + * @return Indicates whether or not this NiFi supports anonymous access. + */ + @ApiModelProperty( + value = "Indicates whether or not this NiFi supports anonymous.", + readOnly = true + ) + public Boolean getSupportsAnonymous() { + return supportsAnonymous; + } + + public void setSupportsAnonymous(Boolean supportsAnonymous) { + this.supportsAnonymous = supportsAnonymous; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessStatusDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessStatusDTO.java new file mode 100644 index 0000000000..712da0ecb1 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessStatusDTO.java @@ -0,0 +1,101 @@ +/* + * 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.api.dto; + +import com.wordnik.swagger.annotations.ApiModelProperty; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds the users access status. + */ +@XmlRootElement(name = "accessStatus") +public class AccessStatusDTO { + + public static enum Status { + + UNKNOWN, + UNREGISTERED, + NOT_ACTIVE, + ACTIVE + } + + private String identity; + private String username; + private String status; + private String message; + + /** + * @return the user identity + */ + @ApiModelProperty( + value = "The user identity.", + readOnly = true + ) + public String getIdentity() { + return identity; + } + + public void setIdentity(String identity) { + this.identity = identity; + } + + /** + * @return the username + */ + @ApiModelProperty( + value = "The username.", + readOnly = true + ) + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + /** + * @return the user access status + */ + @ApiModelProperty( + value = "The user access status.", + readOnly = true + ) + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + /** + * @return additional details about the user access status + */ + @ApiModelProperty( + value = "Additional details about the user access status.", + readOnly = true + ) + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessConfigurationEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessConfigurationEntity.java new file mode 100644 index 0000000000..3af0e493b3 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessConfigurationEntity.java @@ -0,0 +1,43 @@ +/* + * 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.api.entity; + +import javax.xml.bind.annotation.XmlRootElement; +import org.apache.nifi.web.api.dto.AccessConfigurationDTO; + +/** + * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a AccessConfigurationDTO. + */ +@XmlRootElement(name = "accessConfigurationEntity") +public class AccessConfigurationEntity extends Entity { + + private AccessConfigurationDTO config; + + /** + * The AccessConfigurationDTO that is being serialized. + * + * @return The AccessConfigurationDTO object + */ + public AccessConfigurationDTO getConfig() { + return config; + } + + public void setConfig(AccessConfigurationDTO config) { + this.config = config; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessStatusEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessStatusEntity.java new file mode 100644 index 0000000000..f19a26881d --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessStatusEntity.java @@ -0,0 +1,43 @@ +/* + * 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.api.entity; + +import javax.xml.bind.annotation.XmlRootElement; +import org.apache.nifi.web.api.dto.AccessStatusDTO; + +/** + * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a AccessStatusDTO. + */ +@XmlRootElement(name = "accessStatusEntity") +public class AccessStatusEntity extends Entity { + + private AccessStatusDTO accessStatus; + + /** + * The AccessStatusDTO that is being serialized. + * + * @return The AccessStatusDTO object + */ + public AccessStatusDTO getAccessStatus() { + return accessStatus; + } + + public void setAccessStatus(AccessStatusDTO accessStatus) { + this.accessStatus = accessStatus; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/IdentityEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/IdentityEntity.java new file mode 100644 index 0000000000..02991c76e1 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/IdentityEntity.java @@ -0,0 +1,52 @@ +/* + * 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.api.entity; + +import javax.xml.bind.annotation.XmlRootElement; + +/** + * A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds the users identity. + */ +@XmlRootElement(name = "identityEntity") +public class IdentityEntity extends Entity { + + private String userId; + private String identity; + + /** + * @return current user id + */ + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + /** + * @return the user identity being serialized + */ + public String getIdentity() { + return identity; + } + + public void setIdentity(String identity) { + this.identity = identity; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java index 9bbc3a3b9c..db0b35eb54 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; +import org.apache.nifi.authentication.LoginIdentityProvider; import org.apache.nifi.authorization.AuthorityProvider; import org.apache.nifi.controller.ControllerService; @@ -38,9 +39,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Scans through the classpath to load all FlowFileProcessors, - * FlowFileComparators, and ReportingTasks using the service provider API and - * running through all classloaders (root, NARs). + * Scans through the classpath to load all FlowFileProcessors, FlowFileComparators, and ReportingTasks using the service provider API and running through all classloaders (root, NARs). * * @ThreadSafe - is immutable */ @@ -60,6 +59,7 @@ public class ExtensionManager { definitionMap.put(ReportingTask.class, new HashSet()); definitionMap.put(ControllerService.class, new HashSet()); definitionMap.put(AuthorityProvider.class, new HashSet()); + definitionMap.put(LoginIdentityProvider.class, new HashSet()); definitionMap.put(ProvenanceEventRepository.class, new HashSet()); definitionMap.put(ComponentStatusRepository.class, new HashSet()); definitionMap.put(FlowFileRepository.class, new HashSet()); @@ -68,9 +68,7 @@ public class ExtensionManager { } /** - * Loads all FlowFileProcessor, FlowFileComparator, ReportingTask class - * types that can be found on the bootstrap classloader and by creating - * classloaders for all NARs found within the classpath. + * Loads all FlowFileProcessor, FlowFileComparator, ReportingTask class types that can be found on the bootstrap classloader and by creating classloaders for all NARs found within the classpath. */ public static void discoverExtensions() { final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); @@ -113,8 +111,7 @@ public class ExtensionManager { } /** - * Registers extension for the specified type from the specified - * ClassLoader. + * Registers extension for the specified type from the specified ClassLoader. * * @param type the extension type * @param classloaderMap mapping of classname to classloader @@ -152,9 +149,7 @@ public class ExtensionManager { } /** - * Determines the effective classloader for classes of the given type. If - * returns null it indicates the given type is not known or was not - * detected. + * Determines the effective classloader for classes of the given type. If returns null it indicates the given type is not known or was not detected. * * @param classType to lookup the classloader of * @return String of fully qualified class name; null if not a detected type diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java index 9471ba6d80..9e9bd032d8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java @@ -23,6 +23,7 @@ import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; +import org.apache.nifi.authentication.LoginIdentityProvider; import org.apache.nifi.authorization.AuthorityProvider; import org.apache.nifi.components.Validator; @@ -58,6 +59,7 @@ public class NarThreadContextClassLoader extends URLClassLoader { narSpecificClasses.add(StreamCallback.class); narSpecificClasses.add(ControllerService.class); narSpecificClasses.add(AuthorityProvider.class); + narSpecificClasses.add(LoginIdentityProvider.class); narSpecificClasses.add(ProvenanceEventRepository.class); narSpecificClasses.add(ComponentStatusRepository.class); narSpecificClasses.add(FlowFileRepository.class); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/login-identity-providers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/login-identity-providers.xml new file mode 100644 index 0000000000..9868b9deeb --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/login-identity-providers.xml @@ -0,0 +1,92 @@ + + + + + + + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties index 54b5283d43..b25d05a130 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties @@ -25,6 +25,7 @@ nifi.administrative.yield.duration=${nifi.administrative.yield.duration} nifi.bored.yield.duration=${nifi.bored.yield.duration} nifi.authority.provider.configuration.file=${nifi.authority.provider.configuration.file} +nifi.login.identity.provider.configuration.file=${nifi.login.identity.provider.configuration.file} nifi.templates.directory=${nifi.templates.directory} nifi.ui.banner.text=${nifi.ui.banner.text} nifi.ui.autorefresh.interval=${nifi.ui.autorefresh.interval} @@ -124,7 +125,9 @@ nifi.security.truststorePasswd=${nifi.security.truststorePasswd} nifi.security.needClientAuth=${nifi.security.needClientAuth} nifi.security.user.credential.cache.duration=${nifi.security.user.credential.cache.duration} nifi.security.user.authority.provider=${nifi.security.user.authority.provider} +nifi.security.user.login.identity.provider=${nifi.security.user.login.identity.provider} nifi.security.support.new.account.requests=${nifi.security.support.new.account.requests} +nifi.security.anonymous.authorities=${nifi.security.anonymous.authorities} nifi.security.ocsp.responder.url=${nifi.security.ocsp.responder.url} nifi.security.ocsp.responder.certificate=${nifi.security.ocsp.responder.certificate} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java index ecfe2c0c68..d1bd5c879c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java @@ -615,8 +615,12 @@ public class JettyServer implements NiFiServer { private SslContextFactory createSslContextFactory() { final SslContextFactory contextFactory = new SslContextFactory(); - // need client auth - contextFactory.setNeedClientAuth(props.getNeedClientAuth()); + // require client auth when not supporting login or anonymous access + if (StringUtils.isBlank(props.getProperty(NiFiProperties.SECURITY_USER_LOGIN_IDENTITY_PROVIDER)) && props.getAnonymousAuthorities().isEmpty()) { + contextFactory.setNeedClientAuth(true); + } else { + contextFactory.setWantClientAuth(true); + } /* below code sets JSSE system properties when values are provided */ // keystore properties diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ControllerAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ControllerAuditor.java index cede675022..4357633338 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ControllerAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ControllerAuditor.java @@ -78,7 +78,7 @@ public class ControllerAuditor extends NiFiAuditor { // create the config action FlowChangeAction configAction = new FlowChangeAction(); - configAction.setUserIdentity(user.getDn()); + configAction.setUserIdentity(user.getIdentity()); configAction.setUserName(user.getUserName()); configAction.setOperation(Operation.Configure); configAction.setTimestamp(new Date()); @@ -131,7 +131,7 @@ public class ControllerAuditor extends NiFiAuditor { // create the config action FlowChangeAction configAction = new FlowChangeAction(); - configAction.setUserIdentity(user.getDn()); + configAction.setUserIdentity(user.getIdentity()); configAction.setUserName(user.getUserName()); configAction.setOperation(Operation.Configure); configAction.setTimestamp(new Date()); @@ -184,7 +184,7 @@ public class ControllerAuditor extends NiFiAuditor { // create the config action FlowChangeAction configAction = new FlowChangeAction(); - configAction.setUserIdentity(user.getDn()); + configAction.setUserIdentity(user.getIdentity()); configAction.setUserName(user.getUserName()); configAction.setOperation(Operation.Configure); configAction.setTimestamp(new Date()); @@ -237,7 +237,7 @@ public class ControllerAuditor extends NiFiAuditor { // create the config action FlowChangeAction configAction = new FlowChangeAction(); - configAction.setUserIdentity(user.getDn()); + configAction.setUserIdentity(user.getIdentity()); configAction.setUserName(user.getUserName()); configAction.setOperation(Operation.Configure); configAction.setTimestamp(new Date()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ControllerServiceAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ControllerServiceAuditor.java index 0187ee408f..af8428d494 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ControllerServiceAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ControllerServiceAuditor.java @@ -167,7 +167,7 @@ public class ControllerServiceAuditor extends NiFiAuditor { // create a configuration action FlowChangeAction configurationAction = new FlowChangeAction(); - configurationAction.setUserIdentity(user.getDn()); + configurationAction.setUserIdentity(user.getIdentity()); configurationAction.setUserName(user.getUserName()); configurationAction.setOperation(operation); configurationAction.setTimestamp(actionTimestamp); @@ -187,7 +187,7 @@ public class ControllerServiceAuditor extends NiFiAuditor { if (isDisabled != updateIsDisabled) { // create a controller service action FlowChangeAction serviceAction = new FlowChangeAction(); - serviceAction.setUserIdentity(user.getDn()); + serviceAction.setUserIdentity(user.getIdentity()); serviceAction.setUserName(user.getUserName()); serviceAction.setTimestamp(new Date()); serviceAction.setSourceId(controllerService.getIdentifier()); @@ -271,7 +271,7 @@ public class ControllerServiceAuditor extends NiFiAuditor { // create a processor action FlowChangeAction processorAction = new FlowChangeAction(); - processorAction.setUserIdentity(user.getDn()); + processorAction.setUserIdentity(user.getIdentity()); processorAction.setUserName(user.getUserName()); processorAction.setTimestamp(new Date()); processorAction.setSourceId(processor.getIdentifier()); @@ -289,7 +289,7 @@ public class ControllerServiceAuditor extends NiFiAuditor { // create a reporting task action FlowChangeAction reportingTaskAction = new FlowChangeAction(); - reportingTaskAction.setUserIdentity(user.getDn()); + reportingTaskAction.setUserIdentity(user.getIdentity()); reportingTaskAction.setUserName(user.getUserName()); reportingTaskAction.setTimestamp(new Date()); reportingTaskAction.setSourceId(reportingTask.getIdentifier()); @@ -307,7 +307,7 @@ public class ControllerServiceAuditor extends NiFiAuditor { // create a controller service action FlowChangeAction serviceAction = new FlowChangeAction(); - serviceAction.setUserIdentity(user.getDn()); + serviceAction.setUserIdentity(user.getIdentity()); serviceAction.setUserName(user.getUserName()); serviceAction.setTimestamp(new Date()); serviceAction.setSourceId(controllerService.getIdentifier()); @@ -387,7 +387,7 @@ public class ControllerServiceAuditor extends NiFiAuditor { // create the controller service action for adding this controller service action = new FlowChangeAction(); - action.setUserIdentity(user.getDn()); + action.setUserIdentity(user.getIdentity()); action.setUserName(user.getUserName()); action.setOperation(operation); action.setTimestamp(new Date()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/FunnelAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/FunnelAuditor.java index 51cb20cf20..3949028de7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/FunnelAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/FunnelAuditor.java @@ -121,7 +121,7 @@ public class FunnelAuditor extends NiFiAuditor { if (user != null) { // create the action for adding this funnel action = new FlowChangeAction(); - action.setUserIdentity(user.getDn()); + action.setUserIdentity(user.getIdentity()); action.setUserName(user.getUserName()); action.setOperation(operation); action.setTimestamp(new Date()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/PortAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/PortAuditor.java index b07d64f169..e99a1aa043 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/PortAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/PortAuditor.java @@ -205,7 +205,7 @@ public class PortAuditor extends NiFiAuditor { for (ActionDetails detail : configurationDetails) { // create the port action for updating the name FlowChangeAction portAction = new FlowChangeAction(); - portAction.setUserIdentity(user.getDn()); + portAction.setUserIdentity(user.getIdentity()); portAction.setUserName(user.getUserName()); portAction.setOperation(Operation.Configure); portAction.setTimestamp(timestamp); @@ -225,7 +225,7 @@ public class PortAuditor extends NiFiAuditor { if (scheduledState != updatedScheduledState) { // create a processor action FlowChangeAction processorAction = new FlowChangeAction(); - processorAction.setUserIdentity(user.getDn()); + processorAction.setUserIdentity(user.getIdentity()); processorAction.setUserName(user.getUserName()); processorAction.setTimestamp(new Date()); processorAction.setSourceId(updatedPort.getIdentifier()); @@ -323,7 +323,7 @@ public class PortAuditor extends NiFiAuditor { // create the port action for adding this processor action = new FlowChangeAction(); - action.setUserIdentity(user.getDn()); + action.setUserIdentity(user.getIdentity()); action.setUserName(user.getUserName()); action.setOperation(operation); action.setTimestamp(new Date()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessGroupAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessGroupAuditor.java index d56355577d..89871e6a88 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessGroupAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessGroupAuditor.java @@ -140,7 +140,7 @@ public class ProcessGroupAuditor extends NiFiAuditor { // create the port action for updating the name FlowChangeAction processGroupAction = new FlowChangeAction(); - processGroupAction.setUserIdentity(user.getDn()); + processGroupAction.setUserIdentity(user.getIdentity()); processGroupAction.setUserName(user.getUserName()); processGroupAction.setOperation(operation); processGroupAction.setTimestamp(timestamp); @@ -157,7 +157,7 @@ public class ProcessGroupAuditor extends NiFiAuditor { if (processGroupDTO.isRunning() != null) { // create a process group action FlowChangeAction processGroupAction = new FlowChangeAction(); - processGroupAction.setUserIdentity(user.getDn()); + processGroupAction.setUserIdentity(user.getIdentity()); processGroupAction.setUserName(user.getUserName()); processGroupAction.setSourceId(processGroup.getIdentifier()); processGroupAction.setSourceName(processGroup.getName()); @@ -242,7 +242,7 @@ public class ProcessGroupAuditor extends NiFiAuditor { // create the process group action for adding this process group action = new FlowChangeAction(); - action.setUserIdentity(user.getDn()); + action.setUserIdentity(user.getIdentity()); action.setUserName(user.getUserName()); action.setOperation(operation); action.setTimestamp(new Date()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessorAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessorAuditor.java index b8a2c6993f..4f147fbe80 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessorAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ProcessorAuditor.java @@ -177,7 +177,7 @@ public class ProcessorAuditor extends NiFiAuditor { // create a configuration action FlowChangeAction configurationAction = new FlowChangeAction(); - configurationAction.setUserIdentity(user.getDn()); + configurationAction.setUserIdentity(user.getIdentity()); configurationAction.setUserName(user.getUserName()); configurationAction.setOperation(operation); configurationAction.setTimestamp(actionTimestamp); @@ -197,7 +197,7 @@ public class ProcessorAuditor extends NiFiAuditor { if (scheduledState != updatedScheduledState) { // create a processor action FlowChangeAction processorAction = new FlowChangeAction(); - processorAction.setUserIdentity(user.getDn()); + processorAction.setUserIdentity(user.getIdentity()); processorAction.setUserName(user.getUserName()); processorAction.setTimestamp(new Date()); processorAction.setSourceId(processor.getIdentifier()); @@ -294,7 +294,7 @@ public class ProcessorAuditor extends NiFiAuditor { // create the processor action for adding this processor action = new FlowChangeAction(); - action.setUserIdentity(user.getDn()); + action.setUserIdentity(user.getIdentity()); action.setUserName(user.getUserName()); action.setOperation(operation); action.setTimestamp(new Date()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/RelationshipAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/RelationshipAuditor.java index f90d572163..95000d8118 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/RelationshipAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/RelationshipAuditor.java @@ -188,7 +188,7 @@ public class RelationshipAuditor extends NiFiAuditor { // create a configuration action FlowChangeAction configurationAction = new FlowChangeAction(); - configurationAction.setUserIdentity(user.getDn()); + configurationAction.setUserIdentity(user.getIdentity()); configurationAction.setUserName(user.getUserName()); configurationAction.setOperation(Operation.Configure); configurationAction.setTimestamp(actionTimestamp); @@ -353,7 +353,7 @@ public class RelationshipAuditor extends NiFiAuditor { // create a new relationship action action = new FlowChangeAction(); - action.setUserIdentity(user.getDn()); + action.setUserIdentity(user.getIdentity()); action.setUserName(user.getUserName()); action.setOperation(operation); action.setTimestamp(actionTimestamp); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/RemoteProcessGroupAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/RemoteProcessGroupAuditor.java index e145a626ac..5815634701 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/RemoteProcessGroupAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/RemoteProcessGroupAuditor.java @@ -246,7 +246,7 @@ public class RemoteProcessGroupAuditor extends NiFiAuditor { for (ActionDetails detail : details) { // create the port action for updating the name FlowChangeAction remoteProcessGroupAction = new FlowChangeAction(); - remoteProcessGroupAction.setUserIdentity(user.getDn()); + remoteProcessGroupAction.setUserIdentity(user.getIdentity()); remoteProcessGroupAction.setUserName(user.getUserName()); remoteProcessGroupAction.setOperation(Operation.Configure); remoteProcessGroupAction.setTimestamp(timestamp); @@ -267,7 +267,7 @@ public class RemoteProcessGroupAuditor extends NiFiAuditor { if (transmissionState != updatedTransmissionState) { // create a processor action FlowChangeAction remoteProcessGroupAction = new FlowChangeAction(); - remoteProcessGroupAction.setUserIdentity(user.getDn()); + remoteProcessGroupAction.setUserIdentity(user.getIdentity()); remoteProcessGroupAction.setUserName(user.getUserName()); remoteProcessGroupAction.setTimestamp(new Date()); remoteProcessGroupAction.setSourceId(updatedRemoteProcessGroup.getIdentifier()); @@ -356,7 +356,7 @@ public class RemoteProcessGroupAuditor extends NiFiAuditor { // create the remote process group action action = new FlowChangeAction(); - action.setUserIdentity(user.getDn()); + action.setUserIdentity(user.getIdentity()); action.setUserName(user.getUserName()); action.setOperation(operation); action.setTimestamp(new Date()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ReportingTaskAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ReportingTaskAuditor.java index 712f99ae48..77df12ae87 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ReportingTaskAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/ReportingTaskAuditor.java @@ -160,7 +160,7 @@ public class ReportingTaskAuditor extends NiFiAuditor { // create a configuration action FlowChangeAction configurationAction = new FlowChangeAction(); - configurationAction.setUserIdentity(user.getDn()); + configurationAction.setUserIdentity(user.getIdentity()); configurationAction.setUserName(user.getUserName()); configurationAction.setOperation(operation); configurationAction.setTimestamp(actionTimestamp); @@ -180,7 +180,7 @@ public class ReportingTaskAuditor extends NiFiAuditor { if (scheduledState != updatedScheduledState) { // create a reporting task action FlowChangeAction taskAction = new FlowChangeAction(); - taskAction.setUserIdentity(user.getDn()); + taskAction.setUserIdentity(user.getIdentity()); taskAction.setUserName(user.getUserName()); taskAction.setTimestamp(new Date()); taskAction.setSourceId(reportingTask.getIdentifier()); @@ -276,7 +276,7 @@ public class ReportingTaskAuditor extends NiFiAuditor { // create the reporting task action for adding this reporting task action = new FlowChangeAction(); - action.setUserIdentity(user.getDn()); + action.setUserIdentity(user.getIdentity()); action.setUserName(user.getUserName()); action.setOperation(operation); action.setTimestamp(new Date()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/SnippetAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/SnippetAuditor.java index 34382b3630..4b7c38a3be 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/SnippetAuditor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/SnippetAuditor.java @@ -232,7 +232,7 @@ public class SnippetAuditor extends NiFiAuditor { if (user != null) { // create the action for adding this funnel action = new FlowChangeAction(); - action.setUserIdentity(user.getDn()); + action.setUserIdentity(user.getIdentity()); action.setUserName(user.getUserName()); action.setOperation(operation); action.setTimestamp(timestamp); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java index 2d3355ae43..73d76bd0d7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java @@ -1266,6 +1266,13 @@ public interface NiFiServiceFacade { */ Collection getUsers(Boolean grouped); + /** + * Creates a new account request. + * + * @return user + */ + UserDTO createUser(); + /** * Updates the specified user accordingly. * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiConfiguration.java new file mode 100644 index 0000000000..58b0af85b3 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiConfiguration.java @@ -0,0 +1,40 @@ +/* + * 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; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportResource; + +/** + * + */ +@Configuration +@Import({NiFiWebApiSecurityConfiguration.class}) +@ImportResource({"classpath:nifi-context.xml", + "classpath:nifi-administration-context.xml", + "classpath:nifi-cluster-manager-context.xml", + "classpath:nifi-cluster-protocol-context.xml", + "classpath:nifi-web-security-context.xml", + "classpath:nifi-web-api-context.xml"}) +public class NiFiWebApiConfiguration { + + public NiFiWebApiConfiguration() { + super(); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java new file mode 100644 index 0000000000..e8ed267666 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiWebApiSecurityConfiguration.java @@ -0,0 +1,179 @@ +/* + * 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; + +import org.apache.nifi.admin.service.UserService; +import org.apache.nifi.authentication.LoginIdentityProvider; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.NiFiAuthenticationProvider; +import org.apache.nifi.web.security.anonymous.NiFiAnonymousUserFilter; +import org.apache.nifi.web.security.NiFiAuthenticationEntryPoint; +import org.apache.nifi.web.security.jwt.JwtAuthenticationFilter; +import org.apache.nifi.web.security.jwt.JwtService; +import org.apache.nifi.web.security.node.NodeAuthorizedUserFilter; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.x509.X509AuthenticationFilter; +import org.apache.nifi.web.security.x509.X509CertificateExtractor; +import org.apache.nifi.web.security.x509.X509IdentityProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; + +/** + * NiFi Web Api Spring security + */ +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapter { + + private NiFiProperties properties; + private UserService userService; + private AuthenticationUserDetailsService userDetailsService; + private JwtService jwtService; + private X509CertificateExtractor certificateExtractor; + private X509IdentityProvider certificateIdentityProvider; + private LoginIdentityProvider loginIdentityProvider; + + public NiFiWebApiSecurityConfiguration() { + super(true); // disable defaults + } + + @Override + public void configure(WebSecurity webSecurity) throws Exception { + webSecurity + .ignoring() + .antMatchers("/access/**"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .rememberMe().disable() + .exceptionHandling() + .authenticationEntryPoint(new NiFiAuthenticationEntryPoint(properties)) + .and() + .authorizeRequests() + .anyRequest().fullyAuthenticated() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + // cluster authorized user + http.addFilterBefore(buildNodeAuthorizedUserFilter(), AnonymousAuthenticationFilter.class); + + // anonymous + http.anonymous().authenticationFilter(buildAnonymousFilter()); + + // x509 + http.addFilterAfter(buildX509Filter(), AnonymousAuthenticationFilter.class); + + // jwt - consider when configured for log in + if (loginIdentityProvider != null) { + http.addFilterAfter(buildJwtFilter(), AnonymousAuthenticationFilter.class); + } + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + // override xxxBean method so the authentication manager is available in app context (necessary for the method level security) + return super.authenticationManagerBean(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(new NiFiAuthenticationProvider(userDetailsService)); + } + + private NodeAuthorizedUserFilter buildNodeAuthorizedUserFilter() { + final NodeAuthorizedUserFilter nodeFilter = new NodeAuthorizedUserFilter(); + nodeFilter.setProperties(properties); + nodeFilter.setCertificateExtractor(certificateExtractor); + nodeFilter.setCertificateIdentityProvider(certificateIdentityProvider); + return nodeFilter; + } + + private JwtAuthenticationFilter buildJwtFilter() throws Exception { + final JwtAuthenticationFilter jwtFilter = new JwtAuthenticationFilter(); + jwtFilter.setProperties(properties); + jwtFilter.setJwtService(jwtService); + jwtFilter.setAuthenticationManager(authenticationManager()); + return jwtFilter; + } + + private X509AuthenticationFilter buildX509Filter() throws Exception { + final X509AuthenticationFilter x509Filter = new X509AuthenticationFilter(); + x509Filter.setProperties(properties); + x509Filter.setCertificateExtractor(certificateExtractor); + x509Filter.setCertificateIdentityProvider(certificateIdentityProvider); + x509Filter.setAuthenticationManager(authenticationManager()); + return x509Filter; + } + + private AnonymousAuthenticationFilter buildAnonymousFilter() { + final NiFiAnonymousUserFilter anonymousFilter = new NiFiAnonymousUserFilter(); + anonymousFilter.setUserService(userService); + return anonymousFilter; + } + + @Autowired + public void setUserDetailsService(AuthenticationUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Autowired + public void setUserService(UserService userService) { + this.userService = userService; + } + + @Autowired + public void setProperties(NiFiProperties properties) { + this.properties = properties; + } + + @Autowired + public void setJwtService(JwtService jwtService) { + this.jwtService = jwtService; + } + + @Autowired + public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) { + this.loginIdentityProvider = loginIdentityProvider; + } + + @Autowired + public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) { + this.certificateExtractor = certificateExtractor; + } + + @Autowired + public void setCertificateIdentityProvider(X509IdentityProvider certificateIdentityProvider) { + this.certificateIdentityProvider = certificateIdentityProvider; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index bd7482eb54..e7a3328cf3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -170,6 +170,7 @@ import org.apache.nifi.web.api.dto.status.ClusterProcessGroupStatusDTO; import org.apache.nifi.web.api.dto.status.NodeProcessGroupStatusDTO; import org.apache.nifi.web.dao.ControllerServiceDAO; import org.apache.nifi.web.dao.ReportingTaskDAO; +import org.apache.nifi.web.security.user.NewAccountRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; @@ -762,7 +763,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { if (user == null) { throw new WebApplicationException(new Throwable("Unable to access details for current user.")); } - final String userDn = user.getDn(); + final String userDn = user.getIdentity(); if (Node.Status.CONNECTING.name().equalsIgnoreCase(nodeDTO.getStatus())) { clusterManager.requestReconnection(nodeDTO.getNodeId(), userDn); @@ -1782,7 +1783,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { // create a purge action to record that records are being removed FlowChangeAction purgeAction = new FlowChangeAction(); - purgeAction.setUserIdentity(user.getDn()); + purgeAction.setUserIdentity(user.getIdentity()); purgeAction.setUserName(user.getUserName()); purgeAction.setOperation(Operation.Purge); purgeAction.setTimestamp(new Date()); @@ -1819,6 +1820,23 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { } } + @Override + public UserDTO createUser() { + NewAccountRequest newAccountRequest = NiFiUserUtils.getNewAccountRequest(); + + // log the new user account request + logger.info("Requesting new user account for " + newAccountRequest.getUsername()); + + // get the justification + String justification = newAccountRequest.getJustification(); + if (justification == null) { + justification = StringUtils.EMPTY; + } + + // create the pending user account + return dtoFactory.createUserDTO(userService.createPendingUserAccount(newAccountRequest.getUsername(), justification)); + } + @Override public UserDTO updateUser(UserDTO userDto) { NiFiUser user; @@ -2256,7 +2274,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { } final Set allowedUsers = port.getUserAccessControl(); - if (allowedUsers.contains(user.getDn())) { + if (allowedUsers.contains(user.getIdentity())) { return true; } @@ -2632,7 +2650,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final UserDTO groupedUser = groupedUserDTOs.get(user.getUserGroup()); groupedUser.setId(groupedUser.getId() + "," + String.valueOf(user.getId())); groupedUser.setUserName(groupedUser.getUserName() + ", " + user.getUserName()); - groupedUser.setDn(groupedUser.getDn() + ", " + user.getDn()); + groupedUser.setDn(groupedUser.getDn() + ", " + user.getIdentity()); groupedUser.setCreation(getOldestDate(groupedUser.getCreation(), user.getCreation())); groupedUser.setLastAccessed(getNewestDate(groupedUser.getLastAccessed(), user.getLastAccessed())); groupedUser.setLastVerified(getNewestDate(groupedUser.getLastVerified(), user.getLastVerified())); @@ -2731,7 +2749,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { throw new WebApplicationException(new Throwable("Unable to access details for current user.")); } - final String userDn = user.getDn(); + final String userDn = user.getIdentity(); clusterManager.deleteNode(nodeId, userDn); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java index 8e56fc2e68..9502bf2d81 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java @@ -158,19 +158,19 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration @Override public String getCurrentUserDn() { - String userDn = NiFiUser.ANONYMOUS_USER_DN; + String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY; final NiFiUser user = NiFiUserUtils.getNiFiUser(); if (user != null) { - userDn = user.getDn(); + userIdentity = user.getIdentity(); } - return userDn; + return userIdentity; } @Override public String getCurrentUserName() { - String userName = NiFiUser.ANONYMOUS_USER_DN; + String userName = NiFiUser.ANONYMOUS_USER_IDENTITY; final NiFiUser user = NiFiUserUtils.getNiFiUser(); if (user != null) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java index 03ce630e3d..7e9e77e08f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebContext.java @@ -129,19 +129,19 @@ public class StandardNiFiWebContext implements NiFiWebContext { @Override public String getCurrentUserDn() { - String userDn = NiFiUser.ANONYMOUS_USER_DN; + String userIdentity = NiFiUser.ANONYMOUS_USER_IDENTITY; final NiFiUser user = NiFiUserUtils.getNiFiUser(); if (user != null) { - userDn = user.getDn(); + userIdentity = user.getIdentity(); } - return userDn; + return userIdentity; } @Override public String getCurrentUserName() { - String userName = NiFiUser.ANONYMOUS_USER_DN; + String userName = NiFiUser.ANONYMOUS_USER_IDENTITY; final NiFiUser user = NiFiUserUtils.getNiFiUser(); if (user != null) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java new file mode 100644 index 0000000000..e198438d05 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java @@ -0,0 +1,424 @@ +/* + * 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.api; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import io.jsonwebtoken.JwtException; +import org.apache.nifi.util.NiFiProperties; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiParam; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.admin.service.AdministrationException; +import org.apache.nifi.authentication.AuthenticationResponse; +import org.apache.nifi.authentication.LoginCredentials; +import org.apache.nifi.authentication.LoginIdentityProvider; +import org.apache.nifi.authentication.exception.IdentityAccessException; +import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException; +import org.apache.nifi.security.util.CertificateUtils; +import org.apache.nifi.web.api.dto.AccessStatusDTO; +import org.apache.nifi.web.api.dto.AccessConfigurationDTO; +import org.apache.nifi.web.api.dto.RevisionDTO; +import org.apache.nifi.web.api.entity.AccessStatusEntity; +import org.apache.nifi.web.api.entity.AccessConfigurationEntity; +import org.apache.nifi.web.api.request.ClientIdParameter; +import org.apache.nifi.web.security.InvalidAuthenticationException; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; +import org.apache.nifi.web.security.UntrustedProxyException; +import org.apache.nifi.web.security.jwt.JwtService; +import org.apache.nifi.web.security.token.LoginAuthenticationToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.x509.X509CertificateExtractor; +import org.apache.nifi.web.security.x509.X509IdentityProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * RESTful endpoint for managing a cluster. + */ +@Path("/access") +@Api( + value = "/access", + description = "Endpoints for obtaining an access token or checking access status" +) +public class AccessResource extends ApplicationResource { + + private static final Logger logger = LoggerFactory.getLogger(AccessResource.class); + + private static final String AUTHORIZATION = "Authorization"; + + private NiFiProperties properties; + + private LoginIdentityProvider loginIdentityProvider; + private X509CertificateExtractor certificateExtractor; + private X509IdentityProvider certificateIdentityProvider; + private JwtService jwtService; + + private AuthenticationUserDetailsService userDetailsService; + + /** + * Retrieves the access configuration for this NiFi. + * + * @param httpServletRequest the servlet request + * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response. + * @return A accessConfigurationEntity + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + @Path("/config") + @ApiOperation( + value = "Retrieves the access configuration for this NiFi", + response = AccessConfigurationEntity.class + ) + public Response getLoginConfig( + @Context HttpServletRequest httpServletRequest, + @ApiParam( + value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.", + required = false + ) + @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId) { + + final AccessConfigurationDTO accessConfiguration = new AccessConfigurationDTO(); + + // specify whether login should be supported and only support for secure requests + accessConfiguration.setSupportsLogin(loginIdentityProvider != null && httpServletRequest.isSecure()); + accessConfiguration.setSupportsAnonymous(!properties.getAnonymousAuthorities().isEmpty() || !httpServletRequest.isSecure()); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(clientId.getClientId()); + + // create the response entity + final AccessConfigurationEntity entity = new AccessConfigurationEntity(); + entity.setRevision(revision); + entity.setConfig(accessConfiguration); + + // generate the response + return clusterContext(generateOkResponse(entity)).build(); + } + + /** + * Gets the status the client's access. + * + * @param httpServletRequest the servlet request + * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response. + * @return A accessStatusEntity + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) + @Path("") + @ApiOperation( + value = "Gets the status the client's access", + response = AccessStatusEntity.class + ) + @ApiResponses( + value = { + @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(code = 401, message = "Unable to determine access status because the client could not be authenticated."), + @ApiResponse(code = 403, message = "Unable to determine access status because the client is not authorized to make this request."), + @ApiResponse(code = 409, message = "Unable to determine access status because NiFi is not in the appropriate state."), + @ApiResponse(code = 500, message = "Unable to determine access status because an unexpected error occurred.") + } + ) + public Response getAccessStatus( + @Context HttpServletRequest httpServletRequest, + @ApiParam( + value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.", + required = false + ) + @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId) { + + // only consider user specific access over https + if (!httpServletRequest.isSecure()) { + throw new IllegalStateException("User authentication/authorization is only supported when running over HTTPS."); + } + + final AccessStatusDTO accessStatus = new AccessStatusDTO(); + + try { + final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(httpServletRequest); + + // if there is not certificate, consider a token + if (certificates == null) { + // look for an authorization token + final String authorization = httpServletRequest.getHeader(AUTHORIZATION); + + // if there is no authorization header, we don't know the user + if (authorization == null) { + accessStatus.setStatus(AccessStatusDTO.Status.UNKNOWN.name()); + accessStatus.setMessage("No credentials supplied, unknown user."); + } else { + try { + // Extract the Base64 encoded token from the Authorization header + final String token = StringUtils.substringAfterLast(authorization, " "); + final String principal = jwtService.getAuthenticationFromToken(token); + + // set the user identity + accessStatus.setIdentity(principal); + accessStatus.setUsername(CertificateUtils.extractUsername(principal)); + + // without a certificate, this is not a proxied request + final List chain = Arrays.asList(principal); + + // check authorization for this user + checkAuthorization(chain); + + // no issues with authorization + accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name()); + accessStatus.setMessage("Account is active and authorized"); + } catch (JwtException e) { + throw new InvalidAuthenticationException(e.getMessage(), e); + } + } + } else { + try { + final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificates); + + // get the proxy chain and ensure its populated + final List proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(httpServletRequest, authenticationResponse.getIdentity()); + if (proxyChain.isEmpty()) { + logger.error(String.format("Unable to parse the proxy chain %s from the incoming request.", authenticationResponse.getIdentity())); + throw new IllegalArgumentException("Unable to determine the user from the incoming request."); + } + + // set the user identity + accessStatus.setIdentity(proxyChain.get(0)); + accessStatus.setUsername(CertificateUtils.extractUsername(proxyChain.get(0))); + + // ensure the proxy chain is authorized + checkAuthorization(proxyChain); + + // no issues with authorization + accessStatus.setStatus(AccessStatusDTO.Status.ACTIVE.name()); + accessStatus.setMessage("Account is active and authorized"); + } catch (final IllegalArgumentException iae) { + throw new InvalidAuthenticationException(iae.getMessage(), iae); + } + } + } catch (final UsernameNotFoundException unfe) { + accessStatus.setStatus(AccessStatusDTO.Status.UNREGISTERED.name()); + accessStatus.setMessage(String.format("Unregistered user %s", accessStatus.getIdentity())); + } catch (final AccountStatusException ase) { + accessStatus.setStatus(AccessStatusDTO.Status.NOT_ACTIVE.name()); + accessStatus.setMessage(ase.getMessage()); + } catch (final UntrustedProxyException upe) { + throw new AccessDeniedException(upe.getMessage(), upe); + } catch (final AuthenticationServiceException ase) { + throw new AdministrationException(ase.getMessage(), ase); + } + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(clientId.getClientId()); + + // create the entity + final AccessStatusEntity entity = new AccessStatusEntity(); + entity.setRevision(revision); + entity.setAccessStatus(accessStatus); + + return generateOkResponse(entity).build(); + } + + /** + * Checks the status of the proxy. + * + * @param proxyChain the proxy chain + * @throws AuthenticationException if the proxy chain is not authorized + */ + private void checkAuthorization(final List proxyChain) throws AuthenticationException { + userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain)); + } + + /** + * Creates a token for accessing the REST API via username/password. + * + * @param httpServletRequest the servlet request + * @param username the username + * @param password the password + * @return A JWT (string) + */ + @POST + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Produces(MediaType.TEXT_PLAIN) + @Path("/token") + @ApiOperation( + value = "Creates a token for accessing the REST API via username/password", + response = String.class + ) + @ApiResponses( + value = { + @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(code = 403, message = "Client is not authorized to make this request."), + @ApiResponse(code = 409, message = "Unable to create access token because NiFi is not in the appropriate state. (i.e. may not be configured to support username/password login."), + @ApiResponse(code = 500, message = "Unable to create access token because an unexpected error occurred.") + } + ) + public Response createAccessToken( + @Context HttpServletRequest httpServletRequest, + @FormParam("username") String username, + @FormParam("password") String password) { + + // only support access tokens when communicating over HTTPS + if (!httpServletRequest.isSecure()) { + throw new IllegalStateException("Access tokens are only issued over HTTPS."); + } + + // if not configuration for login, don't consider credentials + if (loginIdentityProvider == null) { + throw new IllegalStateException("Username/Password login not supported by this NiFi."); + } + + final LoginAuthenticationToken loginAuthenticationToken; + + final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(httpServletRequest); + + // if there is not certificate, consider login credentials + if (certificates == null) { + // ensure we have login credentials + if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { + throw new IllegalArgumentException("The username and password must be specified."); + } + + try { + // attempt to authenticate + final AuthenticationResponse authenticationResponse = loginIdentityProvider.authenticate(new LoginCredentials(username, password)); + final long maxExpiration = TimeUnit.MILLISECONDS.convert(12, TimeUnit.HOURS); + final long minExpiration = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES); + + long expiration = authenticationResponse.getExpiration(); + if (expiration > maxExpiration) { + expiration = maxExpiration; + + logger.warn(String.format("Max token expiration exceeded. Setting expiration to %s from %s for %s", expiration, + authenticationResponse.getExpiration(), authenticationResponse.getIdentity())); + } else if (expiration < minExpiration) { + expiration = minExpiration; + + logger.warn(String.format("Min token expiration not met. Setting expiration to %s from %s for %s", expiration, + authenticationResponse.getExpiration(), authenticationResponse.getIdentity())); + } + + // create the authentication token + loginAuthenticationToken = new LoginAuthenticationToken(authenticationResponse.getIdentity(), expiration, authenticationResponse.getIssuer()); + } catch (final InvalidLoginCredentialsException ilce) { + throw new IllegalArgumentException("The supplied username and password are not valid.", ilce); + } catch (final IdentityAccessException iae) { + throw new AdministrationException(iae.getMessage(), iae); + } + } else { + // consider a certificate + final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificates); + + // get the proxy chain and ensure its populated + final List proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(httpServletRequest, authenticationResponse.getIdentity()); + if (proxyChain.isEmpty()) { + logger.error(String.format("Unable to parse the proxy chain %s from the incoming request.", authenticationResponse.getIdentity())); + throw new IllegalArgumentException("Unable to determine the user from the incoming request."); + } + + // authorize the proxy if necessary + authorizeProxyIfNecessary(proxyChain); + + // create the authentication token + loginAuthenticationToken = new LoginAuthenticationToken(proxyChain.get(0), authenticationResponse.getExpiration(), authenticationResponse.getIssuer()); + } + + // generate JWT for response + final String token = jwtService.generateSignedToken(loginAuthenticationToken); + + // build the response + final URI uri = URI.create(generateResourceUri("access", "token")); + return generateCreatedResponse(uri, token).build(); + } + + /** + * Ensures the proxyChain is authorized before allowing the user to be authenticated. + * + * @param proxyChain the proxy chain + * @throws AuthenticationException if the proxy chain is not authorized + */ + private void authorizeProxyIfNecessary(final List proxyChain) throws AuthenticationException { + if (proxyChain.size() > 1) { + try { + userDetailsService.loadUserDetails(new NiFiAuthenticationRequestToken(proxyChain)); + } catch (final UsernameNotFoundException unfe) { + // if a username not found exception was thrown, the proxies were authorized and now + // we can issue a new token to the end user which they will use to identify themselves + // when they enter a new account request + } catch (final AuthenticationServiceException ase) { + // throw an administration exception which will return a 500 + throw new AdministrationException(ase.getMessage(), ase); + } catch (final Exception e) { + // any other issue we're going to treat as access denied exception which will return 403 + throw new AccessDeniedException(e.getMessage(), e); + } + } + } + + // setters + public void setProperties(NiFiProperties properties) { + this.properties = properties; + } + + public void setLoginIdentityProvider(LoginIdentityProvider loginIdentityProvider) { + this.loginIdentityProvider = loginIdentityProvider; + } + + public void setJwtService(JwtService jwtService) { + this.jwtService = jwtService; + } + + public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) { + this.certificateExtractor = certificateExtractor; + } + + public void setCertificateIdentityProvider(X509IdentityProvider certificateIdentityProvider) { + this.certificateIdentityProvider = certificateIdentityProvider; + } + + public void setUserDetailsService(AuthenticationUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java index 3bad5e3bdf..d0c36d413d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java @@ -23,7 +23,6 @@ import com.sun.jersey.server.impl.model.method.dispatch.FormDispatchProvider; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; -import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; @@ -45,9 +44,8 @@ import org.apache.nifi.action.Operation; import org.apache.nifi.cluster.context.ClusterContext; import org.apache.nifi.cluster.context.ClusterContextThreadLocal; import org.apache.nifi.cluster.manager.impl.WebClusterManager; -import org.apache.nifi.web.security.DnUtils; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.web.security.x509.X509CertificateExtractor; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.api.entity.Entity; import org.apache.nifi.web.api.request.ClientIdParameter; @@ -55,6 +53,8 @@ import org.apache.nifi.web.util.WebUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.nifi.user.NiFiUser; +import org.apache.nifi.web.security.user.NiFiUserUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; @@ -362,14 +362,12 @@ public abstract class ApplicationResource { result.put(PROXY_SCHEME_HTTP_HEADER, httpServletRequest.getScheme()); } - // if this is a secure request, add the custom headers for proxying user requests - final X509Certificate cert = new X509CertificateExtractor().extractClientCertificate(httpServletRequest); - if (cert != null) { + if (httpServletRequest.isSecure()) { // add the certificate DN to the proxy chain - final String xProxiedEntitiesChain = DnUtils.getXProxiedEntitiesChain(httpServletRequest); - if (StringUtils.isNotBlank(xProxiedEntitiesChain)) { - result.put(PROXIED_ENTITIES_CHAIN_HTTP_HEADER, xProxiedEntitiesChain); + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + if (user != null) { + result.put(PROXIED_ENTITIES_CHAIN_HTTP_HEADER, ProxiedEntitiesUtils.buildProxiedEntitiesChainString(user)); } // add the user's authorities (if any) to the headers diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java index 48837211b8..6e6739d652 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java @@ -79,6 +79,7 @@ import org.apache.nifi.web.api.request.IntegerParameter; import org.apache.nifi.web.api.request.LongParameter; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.web.api.entity.ControllerServiceTypesEntity; +import org.apache.nifi.web.api.entity.IdentityEntity; import org.apache.nifi.web.api.entity.ReportingTaskTypesEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -239,7 +240,7 @@ public class ControllerResource extends ApplicationResource { public ProcessGroupResource getGroupResource( @ApiParam( value = "The id of the process group that is the parent of the requested resource(s). If the desired process group is " - + "the root group an alias 'root' may be used as the process-group-id.", + + "the root group an alias 'root' may be used as the process-group-id.", required = true ) @PathParam("process-group-id") String groupId) { @@ -454,13 +455,13 @@ public class ControllerResource extends ApplicationResource { @ApiOperation( value = "Gets the current revision of this NiFi", notes = "NiFi employs an optimistic locking strategy where the client must include a revision in their request when " - + "performing an update. If the specified revision does not match the current base revision a 409 status code " - + "is returned. The revision is comprised of a clientId and a version number. The version is a simple integer " - + "value that is incremented with each change. Including the most recent version tells NiFi that your working " - + "with the most recent flow. In addition to the version the client who is performing the updates is recorded. " - + "This allows the same client to submit multiple requests without having to wait for the previously ones to " - + "return. Invoking this endpoint will return the current base revision. It is also available when retrieving " - + "a process group and in the response of all mutable requests.", + + "performing an update. If the specified revision does not match the current base revision a 409 status code " + + "is returned. The revision is comprised of a clientId and a version number. The version is a simple integer " + + "value that is incremented with each change. Including the most recent version tells NiFi that your working " + + "with the most recent flow. In addition to the version the client who is performing the updates is recorded. " + + "This allows the same client to submit multiple requests without having to wait for the previously ones to " + + "return. Invoking this endpoint will return the current base revision. It is also available when retrieving " + + "a process group and in the response of all mutable requests.", response = Entity.class, authorizations = { @Authorization(value = "Read Only", type = "ROLE_MONITOR"), @@ -844,6 +845,47 @@ public class ControllerResource extends ApplicationResource { return clusterContext(generateOkResponse(entity)).build(); } + /** + * Retrieves the identity of the user making the request. + * + * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response. + * @return An identityEntity + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + @Path("/identity") + @ApiOperation( + value = "Retrieves the user identity of the user making the request", + response = IdentityEntity.class + ) + public Response getIdentity( + @ApiParam( + value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.", + required = false + ) + @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) ClientIdParameter clientId) { + + // note that the cluster manager will handle this request directly + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + if (user == null) { + throw new WebApplicationException(new Throwable("Unable to access details for current user.")); + } + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(clientId.getClientId()); + + // create the response entity + IdentityEntity entity = new IdentityEntity(); + entity.setRevision(revision); + entity.setUserId(user.getId()); + entity.setIdentity(user.getUserName()); + + // generate the response + return clusterContext(generateOkResponse(entity)).build(); + } + /** * Retrieves the user details, including the authorities, about the user making the request. * @@ -854,14 +896,17 @@ public class ControllerResource extends ApplicationResource { @Consumes(MediaType.WILDCARD) @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @Path("/authorities") - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')") + @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN', 'ROLE_PROXY', 'ROLE_NIFI', 'ROLE_PROVENANCE')") @ApiOperation( value = "Retrieves the user details, including the authorities, about the user making the request", response = AuthorityEntity.class, authorizations = { @Authorization(value = "Read Only", type = "ROLE_MONITOR"), @Authorization(value = "Data Flow Manager", type = "ROLE_DFM"), - @Authorization(value = "Administrator", type = "ROLE_ADMIN") + @Authorization(value = "Administrator", type = "ROLE_ADMIN"), + @Authorization(value = "Proxy", type = "ROLE_PROXY"), + @Authorization(value = "NiFi", type = "ROLE_NIFI"), + @Authorization(value = "Provenance", type = "ROLE_PROVENANCE") } ) @ApiResponses( diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/UserResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/UserResource.java index 4a61ef4dd6..4b9d2ae91f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/UserResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/UserResource.java @@ -16,12 +16,14 @@ */ package org.apache.nifi.web.api; +import com.sun.jersey.api.Responses; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import com.wordnik.swagger.annotations.ApiResponse; import com.wordnik.swagger.annotations.ApiResponses; import com.wordnik.swagger.annotations.Authorization; +import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -39,6 +41,7 @@ import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.HttpMethod; +import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -59,9 +62,11 @@ import org.apache.nifi.web.api.entity.UserSearchResultsEntity; import org.apache.nifi.web.api.entity.UsersEntity; import org.apache.nifi.web.api.request.ClientIdParameter; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.user.NiFiUser; import org.apache.nifi.web.NiFiServiceFacade; import static org.apache.nifi.web.api.ApplicationResource.CLIENT_ID; import org.apache.nifi.web.api.dto.RevisionDTO; +import org.apache.nifi.web.security.user.NiFiUserUtils; import org.springframework.security.access.prepost.PreAuthorize; /** @@ -83,12 +88,35 @@ public class UserResource extends ApplicationResource { private NiFiProperties properties; private NiFiServiceFacade serviceFacade; + @POST + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.TEXT_PLAIN) + @Path("") // necessary due to a bug in swagger + @ApiOperation( + value = "Creates a user", + response = String.class + ) + public Response createUser() { + if (!properties.getSupportNewAccountRequests()) { + return Responses.notFound().entity("This NiFi does not support new account requests.").build(); + } + + final NiFiUser nifiUser = NiFiUserUtils.getNiFiUser(); + if (nifiUser != null) { + throw new IllegalArgumentException("User account already created " + nifiUser.getIdentity()); + } + + // create an account request for the current user + final UserDTO user = serviceFacade.createUser(); + + final String uri = generateResourceUri("controller", "templates", user.getId()); + return generateCreatedResponse(URI.create(uri), "Not authorized. User account created. Authorization pending.").build(); + } + /** * Gets all users that are registered within this Controller. * - * @param clientId Optional client id. If the client id is not specified, a - * new one will be generated. This value (whether specified or generated) is - * included in the response. + * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response. * @param grouped Whether to return the users in their groups. * @return A usersEntity. */ @@ -144,9 +172,7 @@ public class UserResource extends ApplicationResource { /** * Gets the details for the specified user. * - * @param clientId Optional client id. If the client id is not specified, a - * new one will be generated. This value (whether specified or generated) is - * included in the response. + * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response. * @param id The user id. * @return A userEntity. */ @@ -315,12 +341,9 @@ public class UserResource extends ApplicationResource { * Updates the specified user. * * @param httpServletRequest request - * @param clientId Optional client id. If the client id is not specified, a - * new one will be generated. This value (whether specified or generated) is - * included in the response. + * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response. * @param id The id of the user to update. - * @param rawAuthorities Array of authorities to assign to the specified - * user. + * @param rawAuthorities Array of authorities to assign to the specified user. * @param status The status of the specified users account. * @param formParams form params * @return A userEntity @@ -491,9 +514,7 @@ public class UserResource extends ApplicationResource { * * @param httpServletRequest request * @param id The user id - * @param clientId Optional client id. If the client id is not specified, a - * new one will be generated. This value (whether specified or generated) is - * included in the response. + * @param clientId Optional client id. If the client id is not specified, a new one will be generated. This value (whether specified or generated) is included in the response. * @return A userEntity. */ @DELETE diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java index bc6dded606..5d50e7010b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java @@ -39,7 +39,7 @@ public class AccessDeniedExceptionMapper implements ExceptionMapper { + + private static final Logger logger = LoggerFactory.getLogger(InvalidAuthenticationExceptionMapper.class); + + @Override + public Response toResponse(InvalidAuthenticationException exception) { + if (logger.isDebugEnabled()) { + logger.debug(StringUtils.EMPTY, exception); + } + + return Response.status(Response.Status.UNAUTHORIZED).entity(exception.getMessage()).type("text/plain").build(); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java index 87277aefc2..c16653e9b9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java @@ -2382,7 +2382,7 @@ public final class DtoFactory { // create the user UserDTO userDTO = new UserDTO(); userDTO.setId(String.valueOf(user.getId())); - userDTO.setDn(user.getDn()); + userDTO.setDn(user.getIdentity()); userDTO.setUserName(user.getUserName()); userDTO.setUserGroup(user.getUserGroup()); userDTO.setJustification(user.getJustification()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java index 0e3bcac0ad..47ebd3039e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java @@ -113,6 +113,7 @@ import org.apache.nifi.web.api.dto.search.SearchResultsDTO; import org.apache.nifi.web.api.dto.status.ControllerStatusDTO; import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO; import org.apache.nifi.web.api.dto.status.StatusHistoryDTO; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; import org.apache.nifi.web.security.user.NiFiUserUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -822,17 +823,7 @@ public class ControllerFacade { final Map attributes = event.getAttributes(); // calculate the dn chain - final List 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); + final List dnChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(user); // ensure the users in this chain are allowed to download this content final DownloadAuthorization downloadAuthorization = userService.authorizeDownload(dnChain, attributes); @@ -850,7 +841,7 @@ public class ControllerFacade { final String type = event.getAttributes().get(CoreAttributes.MIME_TYPE.key()); // get the content - final InputStream content = flowController.getContent(event, contentDirection, user.getDn(), uri); + final InputStream content = flowController.getContent(event, contentDirection, user.getIdentity(), uri); return new DownloadableContent(filename, type, content); } catch (final ContentNotFoundException cnfe) { throw new ResourceNotFoundException("Unable to find the specified content."); @@ -880,7 +871,7 @@ public class ControllerFacade { } // replay the flow file - final ProvenanceEventRecord event = flowController.replayFlowFile(originalEvent, user.getDn()); + final ProvenanceEventRecord event = flowController.replayFlowFile(originalEvent, user.getIdentity()); // convert the event record return createProvenanceEventDto(event); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java index df271a5754..565e5af08b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardConnectionDAO.java @@ -321,7 +321,7 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO throw new WebApplicationException(new Throwable("Unable to access details for current user.")); } - return queue.dropFlowFiles(dropRequestId, user.getDn()); + return queue.dropFlowFiles(dropRequestId, user.getIdentity()); } @Override diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/filter/RequestLogger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/filter/RequestLogger.java index bfd2df60cf..9f63611b0b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/filter/RequestLogger.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/filter/RequestLogger.java @@ -52,13 +52,13 @@ public class RequestLogger implements Filter { final NiFiUser user = NiFiUserUtils.getNiFiUser(); // get the user details for the log message - String dn = ""; + String identity = ""; if (user != null) { - dn = user.getDn(); + identity = user.getIdentity(); } // log the request attempt - response details will be logged later - logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", dn, request.getMethod(), + logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", identity, request.getMethod(), request.getRequestURL().toString(), request.getRemoteAddr())); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml index e034baa0bf..9f3d2f571e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml @@ -241,12 +241,21 @@ + + + + + + + + + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/webapp/WEB-INF/web.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/webapp/WEB-INF/web.xml index 4ce319eb2f..b57998d397 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/webapp/WEB-INF/web.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/webapp/WEB-INF/web.xml @@ -15,16 +15,13 @@ --> nifi-api + + contextClass + org.springframework.web.context.support.AnnotationConfigWebApplicationContext + contextConfigLocation - - classpath:nifi-context.xml - classpath:nifi-web-api-context.xml - classpath:nifi-web-security-context.xml - classpath:nifi-administration-context.xml - classpath:nifi-cluster-manager-context.xml - classpath:nifi-cluster-protocol-context.xml - + org.apache.nifi.web org.springframework.web.context.ContextLoaderListener diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java new file mode 100644 index 0000000000..82fe73a503 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java @@ -0,0 +1,292 @@ +/* + * 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.integration.accesscontrol; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import javax.net.ssl.SSLContext; +import org.apache.commons.io.FileUtils; +import org.apache.nifi.integration.util.NiFiTestServer; +import org.apache.nifi.integration.util.NiFiTestUser; +import org.apache.nifi.integration.util.SourceTestProcessor; +import org.apache.nifi.nar.ExtensionManager; +import org.apache.nifi.nar.NarClassLoaders; +import org.apache.nifi.security.util.SslContextFactory; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.api.dto.AccessConfigurationDTO; +import org.apache.nifi.web.api.dto.AccessStatusDTO; +import org.apache.nifi.web.api.dto.ProcessorDTO; +import org.apache.nifi.web.api.dto.RevisionDTO; +import org.apache.nifi.web.api.entity.AccessConfigurationEntity; +import org.apache.nifi.web.api.entity.AccessStatusEntity; +import org.apache.nifi.web.api.entity.ProcessorEntity; +import org.apache.nifi.web.util.WebUtils; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Access token endpoint test. + */ +public class AccessTokenEndpointTest { + + private static final String CLIENT_ID = "token-endpoint-id"; + private static final String CONTEXT_PATH = "/nifi-api"; + private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-admin.xml"; + + private static NiFiTestServer SERVER; + private static NiFiTestUser TOKEN_USER; + private static String BASE_URL; + + @BeforeClass + public static void setup() throws Exception { + // configure the location of the nifi properties + File nifiPropertiesFile = new File("src/test/resources/access-control/nifi.properties"); + System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath()); + + // update the flow.xml property + NiFiProperties props = NiFiProperties.getInstance(); + props.setProperty("nifi.flow.configuration.file", FLOW_XML_PATH); + + // delete the database directory to avoid issues with re-registration in testRequestAccessUsingToken + FileUtils.deleteDirectory(props.getDatabaseRepositoryPath().toFile()); + + // load extensions + NarClassLoaders.load(props); + ExtensionManager.discoverExtensions(); + + // start the server + SERVER = new NiFiTestServer("src/main/webapp", CONTEXT_PATH); + SERVER.startServer(); + SERVER.loadFlow(); + + // get the base url + BASE_URL = SERVER.getBaseUrl() + CONTEXT_PATH; + + // create the user + final Client client = WebUtils.createClient(null, createTrustContext(props)); + TOKEN_USER = new NiFiTestUser(client, null); + } + + private static SSLContext createTrustContext(final NiFiProperties props) throws Exception { + return SslContextFactory.createTrustSslContext(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE), + props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray(), + props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE), "TLS"); + } + + // ----------- + // LOGIN CONIG + // ----------- + /** + * Test getting access configuration. + * + * @throws Exception ex + */ + @Test + public void testGetAccessConfig() throws Exception { + String url = BASE_URL + "/access/config"; + + ClientResponse response = TOKEN_USER.testGet(url); + + // ensure the request is successful + Assert.assertEquals(200, response.getStatus()); + + // extract the process group + AccessConfigurationEntity accessConfigEntity = response.getEntity(AccessConfigurationEntity.class); + + // ensure there is content + Assert.assertNotNull(accessConfigEntity); + + // extract the process group dto + AccessConfigurationDTO accessConfig = accessConfigEntity.getConfig(); + + // verify config + Assert.assertTrue(accessConfig.getSupportsLogin()); + Assert.assertFalse(accessConfig.getSupportsAnonymous()); + } + + /** + * Obtains a token and creates a processor using it. + * + * @throws Exception ex + */ + @Test + public void testCreateProcessorUsingToken() throws Exception { + String url = BASE_URL + "/access/token"; + + ClientResponse response = TOKEN_USER.testCreateToken(url, "user@nifi", "whateve"); + + // ensure the request is successful + Assert.assertEquals(201, response.getStatus()); + + // get the token + String token = response.getEntity(String.class); + + // attempt to create a processor with it + createProcessor(token); + } + + private ProcessorDTO createProcessor(final String token) throws Exception { + String url = BASE_URL + "/controller/process-groups/root/processors"; + + // authorization header + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer " + token); + + // create the processor + ProcessorDTO processor = new ProcessorDTO(); + processor.setName("Copy"); + processor.setType(SourceTestProcessor.class.getName()); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(CLIENT_ID); + revision.setVersion(NiFiTestUser.REVISION); + + // create the entity body + ProcessorEntity entity = new ProcessorEntity(); + entity.setRevision(revision); + entity.setProcessor(processor); + + // perform the request + ClientResponse response = TOKEN_USER.testPostWithHeaders(url, entity, headers); + + // ensure the request is successful + Assert.assertEquals(201, response.getStatus()); + + // get the entity body + entity = response.getEntity(ProcessorEntity.class); + + // verify creation + processor = entity.getProcessor(); + Assert.assertEquals("Copy", processor.getName()); + Assert.assertEquals("org.apache.nifi.integration.util.SourceTestProcessor", processor.getType()); + + return processor; + } + + /** + * Verifies the response when bad credentials are specified. + * + * @throws Exception ex + */ + @Test + public void testInvalidCredentials() throws Exception { + String url = BASE_URL + "/access/token"; + + ClientResponse response = TOKEN_USER.testCreateToken(url, "user@nifi", "not a real password"); + + // ensure the request is successful + Assert.assertEquals(400, response.getStatus()); + } + + /** + * Verifies the response when the user is known. + * + * @throws Exception ex + */ + @Test + public void testUnkownUser() throws Exception { + String url = BASE_URL + "/access/token"; + + ClientResponse response = TOKEN_USER.testCreateToken(url, "not a real user", "not a real password"); + + // ensure the request is successful + Assert.assertEquals(400, response.getStatus()); + } + + /** + * Request access using access token. + * + * @throws Exception ex + */ + @Test + public void testRequestAccessUsingToken() throws Exception { + String accessStatusUrl = BASE_URL + "/access"; + String accessTokenUrl = BASE_URL + "/access/token"; + String registrationUrl = BASE_URL + "/controller/users"; + + ClientResponse response = TOKEN_USER.testGet(accessStatusUrl); + + // ensure the request is successful + Assert.assertEquals(200, response.getStatus()); + + AccessStatusEntity accessStatusEntity = response.getEntity(AccessStatusEntity.class); + AccessStatusDTO accessStatus = accessStatusEntity.getAccessStatus(); + + // verify unknown + Assert.assertEquals("UNKNOWN", accessStatus.getStatus()); + + response = TOKEN_USER.testCreateToken(accessTokenUrl, "unregistered-user@nifi", "password"); + + // ensure the request is successful + Assert.assertEquals(201, response.getStatus()); + + // get the token + String token = response.getEntity(String.class); + + // authorization header + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer " + token); + + // check the status with the token + response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers); + + // ensure the request is successful + Assert.assertEquals(200, response.getStatus()); + + accessStatusEntity = response.getEntity(AccessStatusEntity.class); + accessStatus = accessStatusEntity.getAccessStatus(); + + // verify unregistered + Assert.assertEquals("UNREGISTERED", accessStatus.getStatus()); + + response = TOKEN_USER.testRegisterUser(registrationUrl, "Gimme access", headers); + + // ensure the request is successful + Assert.assertEquals(201, response.getStatus()); + + // check the status with the token + response = TOKEN_USER.testGetWithHeaders(accessStatusUrl, null, headers); + + // ensure the request is successful + Assert.assertEquals(200, response.getStatus()); + + accessStatusEntity = response.getEntity(AccessStatusEntity.class); + accessStatus = accessStatusEntity.getAccessStatus(); + + // verify unregistered + Assert.assertEquals("NOT_ACTIVE", accessStatus.getStatus()); + } + + @AfterClass + public static void cleanup() throws Exception { + // shutdown the server + SERVER.shutdownServer(); + SERVER = null; + + // look for the flow.xml + File flow = new File(FLOW_XML_PATH); + if (flow.exists()) { + flow.delete(); + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizationProvider.java index d51b7df52e..d29be9233b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizationProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizationProvider.java @@ -37,7 +37,7 @@ import org.apache.nifi.authorization.DownloadAuthorization; */ public class NiFiTestAuthorizationProvider implements AuthorityProvider { - private Map> users; + private final Map> users; /** * Creates a new FileAuthorizationProvider. @@ -48,6 +48,7 @@ public class NiFiTestAuthorizationProvider implements AuthorityProvider { users.put("CN=Lastname Firstname Middlename monitor, OU=Unknown, OU=Unknown, OU=Unknown, O=Unknown, C=Unknown", EnumSet.of(Authority.ROLE_MONITOR)); users.put("CN=Lastname Firstname Middlename dfm, OU=Unknown, OU=Unknown, OU=Unknown, O=Unknown, C=Unknown", EnumSet.of(Authority.ROLE_DFM)); users.put("CN=Lastname Firstname Middlename admin, OU=Unknown, OU=Unknown, OU=Unknown, O=Unknown, C=Unknown", EnumSet.of(Authority.ROLE_ADMIN)); + users.put("user@nifi", EnumSet.of(Authority.ROLE_DFM)); } @Override diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java new file mode 100644 index 0000000000..c023ce1f6f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestLoginIdentityProvider.java @@ -0,0 +1,75 @@ +/* + * 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.integration.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.apache.nifi.authorization.exception.ProviderCreationException; +import org.apache.nifi.authentication.AuthenticationResponse; +import org.apache.nifi.authentication.LoginCredentials; +import org.apache.nifi.authentication.LoginIdentityProvider; +import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext; +import org.apache.nifi.authentication.LoginIdentityProviderInitializationContext; +import org.apache.nifi.authentication.exception.IdentityAccessException; +import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException; + +/** + * + */ +public class NiFiTestLoginIdentityProvider implements LoginIdentityProvider { + + private final Map users; + + /** + * Creates a new FileAuthorizationProvider. + */ + public NiFiTestLoginIdentityProvider() { + users = new HashMap<>(); + users.put("user@nifi", "whateve"); + users.put("unregistered-user@nifi", "password"); + } + + private void checkUser(final String user, final String password) { + if (!users.containsKey(user)) { + throw new InvalidLoginCredentialsException("Unknown user"); + } + + if (!users.get(user).equals(password)) { + throw new InvalidLoginCredentialsException("Invalid password"); + } + } + + @Override + public AuthenticationResponse authenticate(LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException { + checkUser(credentials.getUsername(), credentials.getPassword()); + return new AuthenticationResponse(credentials.getUsername(), credentials.getUsername(), TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS), getClass().getSimpleName()); + } + + @Override + public void initialize(LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException { + } + + @Override + public void onConfigured(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException { + } + + @Override + public void preDestruction() { + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java index 42b0aaba08..38c2d4153e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java @@ -78,8 +78,12 @@ public class NiFiTestServer { private void createSecureConnector() { org.eclipse.jetty.util.ssl.SslContextFactory contextFactory = new org.eclipse.jetty.util.ssl.SslContextFactory(); - // need client auth - contextFactory.setNeedClientAuth(properties.getNeedClientAuth()); + // require client auth when not supporting login or anonymous access + if (StringUtils.isBlank(properties.getProperty(NiFiProperties.SECURITY_USER_LOGIN_IDENTITY_PROVIDER)) && properties.getAnonymousAuthorities().isEmpty()) { + contextFactory.setNeedClientAuth(true); + } else { + contextFactory.setWantClientAuth(true); + } /* below code sets JSSE system properties when values are provided */ // keystore properties @@ -163,7 +167,6 @@ public class NiFiTestServer { } public Client getClient() { - // create the client return WebUtils.createClient(null, SslContextFactory.createSslContext(properties)); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java index 52f452205a..621dc092a1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestUser.java @@ -22,8 +22,7 @@ import com.sun.jersey.api.client.WebResource; import com.sun.jersey.core.util.MultivaluedMapImpl; import java.util.Map; import javax.ws.rs.core.MediaType; -import org.apache.nifi.web.security.DnUtils; -import org.apache.nifi.web.security.x509.X509AuthenticationFilter; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; /** * @@ -35,9 +34,27 @@ public class NiFiTestUser { private final Client client; private final String proxyDn; - public NiFiTestUser(Client client, String dn) { + public NiFiTestUser(Client client, String proxyDn) { this.client = client; - this.proxyDn = DnUtils.formatProxyDn(dn); + if (proxyDn != null) { + this.proxyDn = ProxiedEntitiesUtils.formatProxyDn(proxyDn); + } else { + this.proxyDn = null; + } + } + + /** + * Conditionally adds the proxied entities chain. + * + * @param builder the resource builder + * @return the resource builder + */ + private WebResource.Builder addProxiedEntities(final WebResource.Builder builder) { + if (proxyDn == null) { + return builder; + } else { + return builder.header(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxyDn); + } } /** @@ -59,6 +76,18 @@ public class NiFiTestUser { * @return response */ public ClientResponse testGet(String url, Map queryParams) { + return testGetWithHeaders(url, queryParams, null); + } + + /** + * Performs a GET using the specified url and query parameters. + * + * @param url url + * @param queryParams params + * @param headers http headers + * @return response + */ + public ClientResponse testGetWithHeaders(String url, Map queryParams, Map headers) { // get the resource WebResource resource = client.resource(url); @@ -69,8 +98,18 @@ public class NiFiTestUser { } } + // get the builder + WebResource.Builder builder = addProxiedEntities(resource.accept(MediaType.APPLICATION_JSON)); + + // append any headers + if (headers != null && !headers.isEmpty()) { + for (String key : headers.keySet()) { + builder = builder.header(key, headers.get(key)); + } + } + // perform the query - return resource.accept(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn).get(ClientResponse.class); + return builder.get(ClientResponse.class); } /** @@ -93,14 +132,34 @@ public class NiFiTestUser { * @throws Exception ex */ public ClientResponse testPost(String url, Object entity) throws Exception { + return testPostWithHeaders(url, entity, null); + } + + /** + * Performs a POST using the specified url and entity body. + * + * @param url url + * @param entity entity + * @param headers http headers + * @return response + * @throws Exception ex + */ + public ClientResponse testPostWithHeaders(String url, Object entity, Map headers) throws Exception { // get the resource - WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON)); // include the request entity if (entity != null) { resourceBuilder = resourceBuilder.entity(entity); } + // append any headers + if (headers != null && !headers.isEmpty()) { + for (String key : headers.keySet()) { + resourceBuilder = resourceBuilder.header(key, headers.get(key)); + } + } + // perform the request return resourceBuilder.post(ClientResponse.class); } @@ -110,18 +169,38 @@ public class NiFiTestUser { * * @param url url * @param entity entity - * @return repsonse + * @return response * @throws Exception ex */ public ClientResponse testPostMultiPart(String url, Object entity) throws Exception { + return testPostMultiPartWithHeaders(url, entity, null); + } + + /** + * Performs a POST using the specified url and entity body. + * + * @param url url + * @param entity entity + * @param headers http headers + * @return response + * @throws Exception ex + */ + public ClientResponse testPostMultiPartWithHeaders(String url, Object entity, Map headers) throws Exception { // get the resource - WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_XML).type(MediaType.MULTIPART_FORM_DATA).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_XML).type(MediaType.MULTIPART_FORM_DATA)); // include the request entity if (entity != null) { resourceBuilder = resourceBuilder.entity(entity); } + // append any headers + if (headers != null && !headers.isEmpty()) { + for (String key : headers.keySet()) { + resourceBuilder = resourceBuilder.header(key, headers.get(key)); + } + } + // perform the request return resourceBuilder.post(ClientResponse.class); } @@ -135,6 +214,19 @@ public class NiFiTestUser { * @throws java.lang.Exception ex */ public ClientResponse testPost(String url, Map formData) throws Exception { + return testPostWithHeaders(url, formData, null); + } + + /** + * Performs a POST using the specified url and form data. + * + * @param url url + * @param formData form data + * @param headers http headers + * @return response + * @throws java.lang.Exception ex + */ + public ClientResponse testPostWithHeaders(String url, Map formData, Map headers) throws Exception { // convert the form data MultivaluedMapImpl entity = new MultivaluedMapImpl(); for (String key : formData.keySet()) { @@ -142,14 +234,20 @@ public class NiFiTestUser { } // get the resource - WebResource.Builder resourceBuilder - = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED)); // add the form data if necessary if (!entity.isEmpty()) { resourceBuilder = resourceBuilder.entity(entity); } + // append any headers + if (headers != null && !headers.isEmpty()) { + for (String key : headers.keySet()) { + resourceBuilder = resourceBuilder.header(key, headers.get(key)); + } + } + // perform the request return resourceBuilder.post(ClientResponse.class); } @@ -163,14 +261,34 @@ public class NiFiTestUser { * @throws java.lang.Exception ex */ public ClientResponse testPut(String url, Object entity) throws Exception { + return testPutWithHeaders(url, entity, null); + } + + /** + * Performs a PUT using the specified url and entity body. + * + * @param url url + * @param entity entity + * @param headers http headers + * @return response + * @throws java.lang.Exception ex + */ + public ClientResponse testPutWithHeaders(String url, Object entity, Map headers) throws Exception { // get the resource - WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_JSON)); // include the request entity if (entity != null) { resourceBuilder = resourceBuilder.entity(entity); } + // append any headers + if (headers != null && !headers.isEmpty()) { + for (String key : headers.keySet()) { + resourceBuilder = resourceBuilder.header(key, headers.get(key)); + } + } + // perform the request return resourceBuilder.put(ClientResponse.class); } @@ -184,6 +302,19 @@ public class NiFiTestUser { * @throws java.lang.Exception ex */ public ClientResponse testPut(String url, Map formData) throws Exception { + return testPutWithHeaders(url, formData, null); + } + + /** + * Performs a PUT using the specified url and form data. + * + * @param url url + * @param formData form data + * @param headers http headers + * @return response + * @throws java.lang.Exception ex + */ + public ClientResponse testPutWithHeaders(String url, Map formData, Map headers) throws Exception { // convert the form data MultivaluedMapImpl entity = new MultivaluedMapImpl(); for (String key : formData.keySet()) { @@ -191,14 +322,20 @@ public class NiFiTestUser { } // get the resource - WebResource.Builder resourceBuilder - = client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED)); // add the form data if necessary if (!entity.isEmpty()) { resourceBuilder = resourceBuilder.entity(entity); } + // append any headers + if (headers != null && !headers.isEmpty()) { + for (String key : headers.keySet()) { + resourceBuilder = resourceBuilder.header(key, headers.get(key)); + } + } + // perform the request return resourceBuilder.put(ClientResponse.class); } @@ -211,24 +348,26 @@ public class NiFiTestUser { * @throws java.lang.Exception ex */ public ClientResponse testDelete(String url) throws Exception { - return testDelete(url, (Object) null); + return testDelete(url, null); } /** * Performs a DELETE using the specified url and entity. * * @param url url - * @param entity entity - * @return repsonse + * @param headers http headers + * @return response * @throws java.lang.Exception ex */ - public ClientResponse testDelete(String url, Object entity) throws Exception { + public ClientResponse testDeleteWithHeaders(String url, Map headers) throws Exception { // get the resource - WebResource.Builder resourceBuilder = client.resource(url).accept(MediaType.APPLICATION_JSON).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn); + WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.APPLICATION_JSON)); - // append any query parameters - if (entity != null) { - resourceBuilder = resourceBuilder.entity(entity); + // append any headers + if (headers != null && !headers.isEmpty()) { + for (String key : headers.keySet()) { + resourceBuilder = resourceBuilder.header(key, headers.get(key)); + } } // perform the query @@ -255,7 +394,56 @@ public class NiFiTestUser { } // perform the request - return resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED).header(X509AuthenticationFilter.PROXY_ENTITIES_CHAIN, proxyDn).delete(ClientResponse.class); + return addProxiedEntities(resource.accept(MediaType.APPLICATION_JSON).type(MediaType.APPLICATION_FORM_URLENCODED)).delete(ClientResponse.class); } + /** + * Attempts to create a token with the specified username and password. + * + * @param url the url + * @param username the username + * @param password the password + * @return response + * @throws Exception ex + */ + public ClientResponse testCreateToken(String url, String username, String password) throws Exception { + // convert the form data + MultivaluedMapImpl entity = new MultivaluedMapImpl(); + entity.add("username", username); + entity.add("password", password); + + // get the resource + WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.TEXT_PLAIN).type(MediaType.APPLICATION_FORM_URLENCODED)).entity(entity); + + // perform the request + return resourceBuilder.post(ClientResponse.class); + } + + /** + * Attempts to create a token with the specified username and password. + * + * @param url the url + * @param justification justification + * @param headers http headers + * @return response + * @throws Exception ex + */ + public ClientResponse testRegisterUser(String url, String justification, Map headers) throws Exception { + // convert the form data + MultivaluedMapImpl entity = new MultivaluedMapImpl(); + entity.add("justification", justification); + + // get the resource + WebResource.Builder resourceBuilder = addProxiedEntities(client.resource(url).accept(MediaType.TEXT_PLAIN).type(MediaType.APPLICATION_FORM_URLENCODED)).entity(entity); + + // append any headers + if (headers != null && !headers.isEmpty()) { + for (String key : headers.keySet()) { + resourceBuilder = resourceBuilder.header(key, headers.get(key)); + } + } + + // perform the request + return resourceBuilder.post(ClientResponse.class); + } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider new file mode 100644 index 0000000000..4b42e4f185 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider @@ -0,0 +1,15 @@ +# 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. +org.apache.nifi.integration.util.NiFiTestLoginIdentityProvider \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml new file mode 100644 index 0000000000..04120c955b --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/login-identity-providers.xml @@ -0,0 +1,24 @@ + + + + + + test-provider + org.apache.nifi.integration.util.NiFiTestLoginIdentityProvider + + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties index 0aa5a14f37..10db651eb8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties @@ -14,16 +14,15 @@ # limitations under the License. # Core Properties # -nifi.version=nifi 0.2.1-SNAPSHOT +nifi.version=nifi version nifi.flow.configuration.file= nifi.flow.configuration.archive.dir=target/archive nifi.flowcontroller.autoResumeState=true nifi.flowcontroller.graceful.shutdown.period=10 sec nifi.flowservice.writedelay.interval=2 sec -nifi.reporting.task.configuration.file=target/test-classes/access-control/reporting-tasks.xml -nifi.controller.service.configuration.file=target/test-classes/access-control/controller-services.xml nifi.authority.provider.configuration.file=target/test-classes/access-control/authority-providers.xml +nifi.login.identity.provider.configuration.file=target/test-classes/access-control/login-identity-providers.xml nifi.templates.directory=target/test-classes/access-control/templates nifi.ui.banner.text=TEST BANNER nifi.ui.autorefresh.interval=30 sec @@ -93,10 +92,11 @@ nifi.security.truststoreType=JKS nifi.security.truststorePasswd=localtest nifi.security.needClientAuth=true nifi.security.user.authority.provider=test-provider +nifi.security.user.login.identity.provider=test-provider nifi.security.authorizedUsers.file=target/test-classes/access-control/users.xml nifi.security.user.credential.cache.duration=1 hr nifi.security.support.new.account.requests= -nifi.security.default.user.roles= +nifi.security.anonymous.authorities= # cluster common properties (cluster manager and nodes must have same values) # nifi.cluster.protocol.heartbeat.interval=5 sec diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml index daeef5cc17..ee18bffda7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/pom.xml @@ -22,11 +22,49 @@ org.apache.nifi nifi-web-security + + + + src/main/resources + + + src/main/xsd + + + + + org.codehaus.mojo + jaxb2-maven-plugin + + + current + + xjc + + + org.apache.nifi.authentication.generated + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + **/authentication/generated/*.java, + + + + org.apache.nifi nifi-administration + + org.apache.nifi + nifi-nar-utils + org.apache.nifi nifi-api @@ -39,6 +77,11 @@ org.apache.nifi nifi-framework-core + + io.jsonwebtoken + jjwt + 0.6.0 + org.bouncycastle bcprov-jdk16 diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java deleted file mode 100644 index f3bd11e774..0000000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/DnUtils.java +++ /dev/null @@ -1,85 +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.web.security; - -import java.security.cert.X509Certificate; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import org.apache.nifi.web.security.x509.SubjectDnX509PrincipalExtractor; -import org.apache.nifi.web.security.x509.X509CertificateExtractor; -import org.apache.commons.lang3.StringUtils; - -/** - * - */ -public class DnUtils { - - private static final Pattern proxyChainPattern = Pattern.compile("<(.*?)>"); - - /** - * @param request http request - * @return the X-ProxiedEntitiesChain from the specified request - */ - public static String getXProxiedEntitiesChain(final HttpServletRequest request) { - String xProxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain"); - final X509Certificate cert = new X509CertificateExtractor().extractClientCertificate(request); - if (cert != null) { - final SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); - final String extractedPrincipal = principalExtractor.extractPrincipal(cert).toString(); - final String formattedPrincipal = formatProxyDn(extractedPrincipal); - if (StringUtils.isBlank(xProxiedEntitiesChain)) { - xProxiedEntitiesChain = formattedPrincipal; - } else { - xProxiedEntitiesChain += formattedPrincipal; - } - } - - return xProxiedEntitiesChain; - } - - /** - * Formats the specified DN to be set as a HTTP header using well known - * conventions. - * - * @param dn raw dn - * @return the dn formatted as an HTTP header - */ - public static String formatProxyDn(String dn) { - return "<" + dn + ">"; - } - - /** - * Tokenizes the specified proxy chain. - * - * @param rawProxyChain raw chain - * @return tokenized proxy chain - */ - public static Deque tokenizeProxyChain(String rawProxyChain) { - final Deque dnList = new ArrayDeque<>(); - - // parse the proxy chain - final Matcher rawProxyChainMatcher = proxyChainPattern.matcher(rawProxyChain); - while (rawProxyChainMatcher.find()) { - dnList.push(rawProxyChainMatcher.group(1)); - } - - return dnList; - } -} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java new file mode 100644 index 0000000000..1065152f9f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/InvalidAuthenticationException.java @@ -0,0 +1,35 @@ +/* + * 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; + +import org.springframework.security.core.AuthenticationException; + +/** + * Thrown if the authentication of a given request is invalid. For instance, + * an expired certificate or token. + */ +public class InvalidAuthenticationException extends AuthenticationException { + + public InvalidAuthenticationException(String msg) { + super(msg); + } + + public InvalidAuthenticationException(String msg, Throwable t) { + super(msg, t); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.java similarity index 58% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.java rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.java index cd5f1ac686..ef1dfb26ea 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authentication/NiFiAuthenticationEntryPoint.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationEntryPoint.java @@ -14,28 +14,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.nifi.web.security.authentication; +package org.apache.nifi.web.security; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.WebAttributes; /** - * This is our own implementation of - * org.springframework.security.web.AuthenticationEntryPoint that allows us to - * send the response to the client exactly how we want to and log the results. + * This is our own implementation of org.springframework.security.web.AuthenticationEntryPoint that allows us to send the response to the client exactly how we want to and log the results. */ public class NiFiAuthenticationEntryPoint implements AuthenticationEntryPoint { private static final Logger logger = LoggerFactory.getLogger(NiFiAuthenticationEntryPoint.class); + private final NiFiProperties properties; + + public NiFiAuthenticationEntryPoint(NiFiProperties properties) { + this.properties = properties; + } + /** * Always returns a 403 error code to the client. * @@ -47,23 +52,20 @@ public class NiFiAuthenticationEntryPoint implements AuthenticationEntryPoint { */ @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException, ServletException { - // get the last exception - the exception that is being passed in is a generic no credentials found - // exception because the authentication could not be found in the security context. the actual cause - // of the problem is stored in the session as the authentication_exception - Object authenticationException = request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); + // if the content type is not set, mark as access denied + if (StringUtils.isBlank(response.getContentType())) { + // write the response message + PrintWriter out = response.getWriter(); + response.setContentType("text/plain"); - // log request result - if (authenticationException instanceof AuthenticationException) { - ae = (AuthenticationException) authenticationException; - logger.info(String.format("Rejecting access to web api: %s", ae.getMessage())); + // return authorized if the request is secure and this nifi supports new account requests + if (request.isSecure() && properties.getSupportNewAccountRequests()) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + out.println("Not authorized."); + } else { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + out.println("Access is denied."); + } } - - // set the response status - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - response.setContentType("text/plain"); - - // write the response message - PrintWriter out = response.getWriter(); - out.println("Access is denied."); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java new file mode 100644 index 0000000000..f09d6101c1 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationFilter.java @@ -0,0 +1,209 @@ +/* + * 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; + +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.user.NiFiUser; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.user.NiFiUserUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.AccountStatusException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * + */ +public abstract class NiFiAuthenticationFilter implements Filter { + + private static final Logger logger = LoggerFactory.getLogger(NiFiAuthenticationFilter.class); + + private AuthenticationManager authenticationManager; + private NiFiProperties properties; + + @Override + public void init(final FilterConfig filterConfig) throws ServletException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { + if (logger.isDebugEnabled()) { + logger.debug("Checking secure context token: " + SecurityContextHolder.getContext().getAuthentication()); + } + + if (requiresAuthentication((HttpServletRequest) request)) { + authenticate((HttpServletRequest) request, (HttpServletResponse) response); + } + + chain.doFilter(request, response); + } + + private boolean requiresAuthentication(final HttpServletRequest request) { + // continue attempting authorization if the user is anonymous + if (isAnonymousUser()) { + return true; + } + + // or there is no user yet + return NiFiUserUtils.getNiFiUser() == null && NiFiUserUtils.getNewAccountRequest() == null; + } + + private boolean isAnonymousUser() { + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + return user != null && NiFiUser.ANONYMOUS_USER_IDENTITY.equals(user.getIdentity()); + } + + private void authenticate(final HttpServletRequest request, final HttpServletResponse response) throws IOException { + try { + final NiFiAuthenticationRequestToken authenticated = attemptAuthentication(request, response); + if (authenticated != null) { + // log the request attempt - response details will be logged later + logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", + ProxiedEntitiesUtils.formatProxyDn(StringUtils.join(authenticated.getChain(), "><")), request.getMethod(), + request.getRequestURL().toString(), request.getRemoteAddr())); + + final Authentication authorized = authenticationManager.authenticate(authenticated); + successfulAuthorization(request, response, authorized); + } + } catch (final AuthenticationException ae) { + if (!isAnonymousUser()) { + unsuccessfulAuthorization(request, response, ae); + } + } + } + + public abstract NiFiAuthenticationRequestToken attemptAuthentication(HttpServletRequest request, HttpServletResponse response); + + protected void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) { + if (logger.isDebugEnabled()) { + logger.debug("Authentication success: " + authResult); + } + + SecurityContextHolder.getContext().setAuthentication(authResult); + ProxiedEntitiesUtils.successfulAuthorization(request, response, authResult); + } + + protected void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException { + // populate the response + ProxiedEntitiesUtils.unsuccessfulAuthorization(request, response, ae); + + // set the response status + response.setContentType("text/plain"); + + // write the response message + PrintWriter out = response.getWriter(); + + // use the type of authentication exception to determine the response code + if (ae instanceof UsernameNotFoundException) { + if (properties.getSupportNewAccountRequests()) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + out.println("Not authorized."); + } else { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + out.println("Access is denied."); + } + } else if (ae instanceof InvalidAuthenticationException) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + out.println(ae.getMessage()); + } else if (ae instanceof AccountStatusException) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + out.println(ae.getMessage()); + } else if (ae instanceof UntrustedProxyException) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + out.println(ae.getMessage()); + } else if (ae instanceof AuthenticationServiceException) { + logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + out.println(String.format("Unable to authorize: %s", ae.getMessage())); + } else { + logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + out.println("Access is denied."); + } + + // log the failure + logger.info(String.format("Rejecting access to web api: %s", ae.getMessage())); + + // optionally log the stack trace + if (logger.isDebugEnabled()) { + logger.debug(StringUtils.EMPTY, ae); + } + } + + /** + * Determines if the specified request is attempting to register a new user account. + * + * @param request http request + * @return true if new user + */ + protected final boolean isNewAccountRequest(HttpServletRequest request) { + if ("POST".equalsIgnoreCase(request.getMethod())) { + String path = request.getPathInfo(); + if (StringUtils.isNotBlank(path)) { + if ("/controller/users".equals(path)) { + return true; + } + } + } + return false; + } + + /** + * Extracts the justification from the specified request. + * + * @param request The request + * @return The justification + */ + protected final String getJustification(HttpServletRequest request) { + // get the justification + String justification = request.getParameter("justification"); + if (justification == null) { + justification = StringUtils.EMPTY; + } + return justification; + } + + @Override + public void destroy() { + } + + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public void setProperties(NiFiProperties properties) { + this.properties = properties; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java new file mode 100644 index 0000000000..eb0684b0d4 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java @@ -0,0 +1,73 @@ +/* + * 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; + +import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NewAccountAuthenticationToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NiFiAuthorizationToken; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * + */ +public class NiFiAuthenticationProvider implements AuthenticationProvider { + + private final AuthenticationUserDetailsService userDetailsService; + + public NiFiAuthenticationProvider(final AuthenticationUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + final NiFiAuthenticationRequestToken request = (NiFiAuthenticationRequestToken) authentication; + + try { + // defer to the nifi user details service to authorize the user + final UserDetails userDetails = userDetailsService.loadUserDetails(request); + + // build an authentication for accesing nifi + final NiFiAuthorizationToken result = new NiFiAuthorizationToken(userDetails); + result.setDetails(request.getDetails()); + return result; + } catch (final UsernameNotFoundException unfe) { + // if the authentication request is for a new account and it could not be authorized because the user was not found, + // return the token so the new account could be created. this must go here toe nsure that any proxies have been authorized + if (isNewAccountAuthenticationToken(request)) { + return new NewAccountAuthenticationToken(((NewAccountAuthenticationRequestToken) authentication).getNewAccountRequest()); + } else { + throw unfe; + } + } + } + + private boolean isNewAccountAuthenticationToken(final Authentication authentication) { + return NewAccountAuthenticationRequestToken.class.isAssignableFrom(authentication.getClass()); + } + + @Override + public boolean supports(Class authentication) { + return NiFiAuthenticationRequestToken.class.isAssignableFrom(authentication); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java new file mode 100644 index 0000000000..1b2f28ad6f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java @@ -0,0 +1,147 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.user.NiFiUser; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + +/** + * + */ +public class ProxiedEntitiesUtils { + + public static final String PROXY_ENTITIES_CHAIN = "X-ProxiedEntitiesChain"; + public static final String PROXY_ENTITIES_ACCEPTED = "X-ProxiedEntitiesAccepted"; + public static final String PROXY_ENTITIES_DETAILS = "X-ProxiedEntitiesDetails"; + + private static final Pattern proxyChainPattern = Pattern.compile("<(.*?)>"); + + /** + * Formats the specified DN to be set as a HTTP header using well known conventions. + * + * @param dn raw dn + * @return the dn formatted as an HTTP header + */ + public static String formatProxyDn(String dn) { + return "<" + dn + ">"; + } + + /** + * Tokenizes the specified proxy chain. + * + * @param rawProxyChain raw chain + * @return tokenized proxy chain + */ + public static List tokenizeProxiedEntitiesChain(String rawProxyChain) { + final List proxyChain = new ArrayList<>(); + final Matcher rawProxyChainMatcher = proxyChainPattern.matcher(rawProxyChain); + while (rawProxyChainMatcher.find()) { + proxyChain.add(rawProxyChainMatcher.group(1)); + } + + return proxyChain; + } + + /** + * Builds the proxy chain for the specified user. + * + * @param user The current user + * @return The proxy chain for that user in String form + */ + public static String buildProxiedEntitiesChainString(final NiFiUser user) { + // calculate the dn chain + final List proxyChain = buildProxiedEntitiesChain(user); + return formatProxyDn(StringUtils.join(proxyChain, "><")); + } + + /** + * Builds the proxy chain for the specified user. + * + * @param user The current user + * @return The proxy chain for that user in List form + */ + public static List buildProxiedEntitiesChain(final NiFiUser user) { + // calculate the dn chain + final List proxyChain = new ArrayList<>(); + + // build the dn chain + NiFiUser chainedUser = user; + do { + // add the entry for this user + proxyChain.add(chainedUser.getIdentity()); + + // go to the next user in the chain + chainedUser = chainedUser.getChain(); + } while (chainedUser != null); + + return proxyChain; + } + + /** + * Builds the proxy chain from the specified request and user. + * + * @param request the request + * @param username the username + * @return the proxy chain in list form + */ + public static List buildProxiedEntitiesChain(final HttpServletRequest request, final String username) { + final String chain = buildProxiedEntitiesChainString(request, username); + return tokenizeProxiedEntitiesChain(chain); + } + + /** + * Builds the dn chain from the specified request and user. + * + * @param request the request + * @param username the username + * @return the dn chain in string form + */ + public static String buildProxiedEntitiesChainString(final HttpServletRequest request, final String username) { + String principal; + if (username.startsWith("<") && username.endsWith(">")) { + principal = username; + } else { + principal = formatProxyDn(username); + } + + // look for a proxied user + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + principal = request.getHeader(PROXY_ENTITIES_CHAIN) + principal; + } + return principal; + } + + public static void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) { + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + response.setHeader(PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString()); + } + } + + public static void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { + if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { + response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage()); + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java index 295f09c7d1..e67ed2c5b2 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/anonymous/NiFiAnonymousUserFilter.java @@ -16,25 +16,22 @@ */ package org.apache.nifi.web.security.anonymous; -import java.util.ArrayList; -import java.util.List; +import java.util.EnumSet; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; 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.web.security.user.NiFiUserDetails; -import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.token.NiFiAuthorizationToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; /** - * Custom AnonymouseAuthenticationFilter used to grant additional authorities - * depending on the current operating mode. + * Custom AnonymouseAuthenticationFilter used to grant additional authorities depending on the current operating mode. */ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter { @@ -42,7 +39,6 @@ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter { private static final String ANONYMOUS_KEY = "anonymousNifiKey"; - private NiFiProperties properties; private UserService userService; public NiFiAnonymousUserFilter() { @@ -51,51 +47,37 @@ public class NiFiAnonymousUserFilter extends AnonymousAuthenticationFilter { @Override protected Authentication createAuthentication(HttpServletRequest request) { - Authentication authentication; + Authentication authentication = null; + try { // load the anonymous user from the database - NiFiUser user = userService.getUserByDn(NiFiUser.ANONYMOUS_USER_DN); - NiFiUserDetails userDetails = new NiFiUserDetails(user); + NiFiUser user = userService.getUserByDn(NiFiUser.ANONYMOUS_USER_IDENTITY); - // get the granted authorities - List authorities = new ArrayList<>(userDetails.getAuthorities()); - authentication = new AnonymousAuthenticationToken(ANONYMOUS_KEY, userDetails, authorities); + // if this is an unsecure request allow full access + if (!request.isSecure()) { + user.getAuthorities().addAll(EnumSet.allOf(Authority.class)); + } + + // only create an authentication token if the anonymous user has some authorities + if (!user.getAuthorities().isEmpty()) { + NiFiUserDetails userDetails = new NiFiUserDetails(user); + + // get the granted authorities + authentication = new NiFiAuthorizationToken(userDetails); + } } catch (AdministrationException ase) { // record the issue anonymousUserFilterLogger.warn("Unable to load anonymous user from accounts database: " + ase.getMessage()); if (anonymousUserFilterLogger.isDebugEnabled()) { anonymousUserFilterLogger.warn(StringUtils.EMPTY, ase); } - - // defer to the base implementation - authentication = super.createAuthentication(request); } return authentication; } - /** - * Only supports anonymous users for non-secure requests or one way ssl. - * - * @param request request - * @return true if allowed - */ - @Override - protected boolean applyAnonymousForThisRequest(HttpServletRequest request) { - // anonymous for non secure requests - if ("http".equalsIgnoreCase(request.getScheme())) { - return true; - } - - return !properties.getNeedClientAuth(); - } - /* setters */ public void setUserService(UserService userService) { this.userService = userService; } - public void setProperties(NiFiProperties properties) { - this.properties = properties; - } - } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java index 95b466937d..23d9e61867 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationService.java @@ -16,34 +16,35 @@ */ package org.apache.nifi.web.security.authorization; -import java.util.Deque; -import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import org.apache.commons.lang3.StringUtils; 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.web.security.DnUtils; import org.apache.nifi.user.NiFiUser; -import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.security.UntrustedProxyException; import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.security.authentication.AccountStatusException; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * UserDetailsService that will verify user identity and grant user authorities. */ -public class NiFiAuthorizationService implements UserDetailsService { +public class NiFiAuthorizationService implements AuthenticationUserDetailsService { private static final Logger logger = LoggerFactory.getLogger(NiFiAuthorizationService.class); @@ -53,35 +54,33 @@ public class NiFiAuthorizationService implements UserDetailsService { /** * 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). + * 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 proxy chain + * @param request request * @return user details * @throws UsernameNotFoundException ex * @throws org.springframework.dao.DataAccessException ex */ @Override - public synchronized UserDetails loadUserByUsername(String rawProxyChain) throws UsernameNotFoundException, DataAccessException { + public synchronized UserDetails loadUserDetails(NiFiAuthenticationRequestToken request) throws UsernameNotFoundException, DataAccessException { NiFiUserDetails userDetails = null; - final Deque dnList = DnUtils.tokenizeProxyChain(rawProxyChain); + final List chain = new ArrayList<>(request.getChain()); // ensure valid input - if (dnList.size() == 0) { - logger.warn("Malformed proxy chain: " + rawProxyChain); + if (chain.isEmpty()) { + logger.warn("Malformed proxy chain: " + StringUtils.join(request.getChain())); 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(); + for (final ListIterator chainIter = request.getChain().listIterator(chain.size()); chainIter.hasPrevious();) { + final String dn = chainIter.previous(); // if there is another dn after this one, this dn is a proxy for the request - if (dnIter.hasNext()) { + if (chainIter.hasPrevious()) { try { // get the user details for the proxy final NiFiUserDetails proxyDetails = getNiFiUserDetails(dn); @@ -108,9 +107,6 @@ public class NiFiAuthorizationService implements UserDetailsService { // attempt to create a new user account for the proxying client userService.createPendingUserAccount(dn, "Automatic account request generated for unknown proxy."); - - // propagate the exception to return the appropriate response - 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) { @@ -121,10 +117,10 @@ public class NiFiAuthorizationService implements UserDetailsService { 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())); - throw new UntrustedProxyException(String.format("Untrusted proxy '%s' must be authorized with '%s'.", dn, Authority.ROLE_PROXY.toString())); } + + logger.warn(String.format("Untrusted proxy '%s' must be authorized with '%s' authority: %s", dn, Authority.ROLE_PROXY.toString(), unfe.getMessage())); + throw new UntrustedProxyException(String.format("Untrusted proxy '%s' must be authorized with '%s'.", dn, Authority.ROLE_PROXY.toString())); } catch (AuthenticationException ae) { logger.warn(String.format("Untrusted proxy '%s' must be authorized with '%s' authority: %s", dn, Authority.ROLE_PROXY.toString(), ae.getMessage())); throw new UntrustedProxyException(String.format("Untrusted proxy '%s' must be authorized with '%s'.", dn, Authority.ROLE_PROXY.toString())); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000000..20675fbd2e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,80 @@ +/* + * 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.jwt; + +import io.jsonwebtoken.JwtException; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.web.security.NiFiAuthenticationFilter; +import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.user.NewAccountRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Arrays; +import org.apache.nifi.web.security.InvalidAuthenticationException; + +/** + */ +public class JwtAuthenticationFilter extends NiFiAuthenticationFilter { + + private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); + + private static final String AUTHORIZATION = "Authorization"; + + private JwtService jwtService; + + @Override + public NiFiAuthenticationRequestToken attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { + // only suppport jwt login when running securely + if (!request.isSecure()) { + return null; + } + + // TODO: Refactor request header extraction logic to shared utility as it is duplicated in AccessResource + + // get the principal out of the user token + final String authorization = request.getHeader(AUTHORIZATION); + + // if there is no authorization header, we don't know the user + if (authorization == null) { + return null; + } else { + // Extract the Base64 encoded token from the Authorization header + final String token = StringUtils.substringAfterLast(authorization, " "); + + try { + final String jwtPrincipal = jwtService.getAuthenticationFromToken(token); + + if (isNewAccountRequest(request)) { + return new NewAccountAuthenticationRequestToken(new NewAccountRequest(Arrays.asList(jwtPrincipal), getJustification(request))); + } else { + return new NiFiAuthenticationRequestToken(Arrays.asList(jwtPrincipal)); + } + } catch (JwtException e) { + throw new InvalidAuthenticationException(e.getMessage(), e); + } + } + } + + public void setJwtService(JwtService jwtService) { + this.jwtService = jwtService; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java new file mode 100644 index 0000000000..9635354edf --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtService.java @@ -0,0 +1,162 @@ +/* + * 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.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.SigningKeyResolverAdapter; +import io.jsonwebtoken.UnsupportedJwtException; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.admin.service.AdministrationException; +import org.apache.nifi.admin.service.KeyService; +import org.apache.nifi.web.security.token.LoginAuthenticationToken; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.util.Calendar; +import org.apache.nifi.key.Key; + +/** + * + */ +public class JwtService { + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JwtService.class); + + private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256; + private static final String KEY_ID_CLAIM = "kid"; + private static final String USERNAME_CLAIM = "preferred_username"; + + private final KeyService keyService; + + public JwtService(final KeyService keyService) { + this.keyService = keyService; + } + + public String getAuthenticationFromToken(final String base64EncodedToken) throws JwtException { + // The library representations of the JWT should be kept internal to this service. + try { + final Jws jws = parseTokenFromBase64EncodedString(base64EncodedToken); + + if (jws == null) { + throw new JwtException("Unable to parse token"); + } + + // Additional validation that subject is present + if (StringUtils.isEmpty(jws.getBody().getSubject())) { + throw new JwtException("No subject available in token"); + } + + // TODO: Validate issuer against active registry? + if (StringUtils.isEmpty(jws.getBody().getIssuer())) { + // TODO: Remove after testing +// logger.info("Decoded JWT payload: " + jws.toString()); + throw new JwtException("No issuer available in token"); + } + return jws.getBody().getSubject(); + } catch (JwtException e) { + logger.debug("The Base64 encoded JWT: " + base64EncodedToken); + final String errorMessage = "There was an error validating the JWT"; + logger.error(errorMessage, e); + throw e; + } + } + + private Jws parseTokenFromBase64EncodedString(final String base64EncodedToken) throws JwtException { + try { + return Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() { + @Override + public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + final String identity = claims.getSubject(); + + // Get the key based on the key id in the claims + final Integer keyId = claims.get(KEY_ID_CLAIM, Integer.class); + final Key key = keyService.getKey(keyId); + + // Ensure we were able to find a key that was previously issued by this key service for this user + if (key == null || key.getKey() == null) { + throw new UnsupportedJwtException("Unable to determine signing key for " + identity + " [kid: " + keyId + "]"); + } + + return key.getKey().getBytes(StandardCharsets.UTF_8); + } + }).parseClaimsJws(base64EncodedToken); + } catch (final MalformedJwtException | UnsupportedJwtException | SignatureException | ExpiredJwtException | IllegalArgumentException | AdministrationException e) { + // TODO: Exercise all exceptions to ensure none leak key material to logs + final String errorMessage = "There was an error validating the JWT"; + throw new JwtException(errorMessage, e); + } + } + + /** + * 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 + * @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) { + 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 Object principal = authenticationToken.getPrincipal(); + 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(); + logger.error(errorMessage); + throw new JwtException(errorMessage); + } + + // Create a JWT with the specified authentication + final String identity = principal.toString(); + final String username = authenticationToken.getName(); + + 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); + + // 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()) + .claim(USERNAME_CLAIM, username) + .claim(KEY_ID_CLAIM, key.getId()) + .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; + logger.error(errorMessage, e); + throw new JwtException(errorMessage, e); + } + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/node/NodeAuthorizedUserFilter.java similarity index 65% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.java rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/node/NodeAuthorizedUserFilter.java index 80feed7123..a3e6c3c1b0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/authorization/NodeAuthorizedUserFilter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/node/NodeAuthorizedUserFilter.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.nifi.web.security.authorization; +package org.apache.nifi.web.security.node; import java.io.IOException; import java.io.Serializable; @@ -26,34 +26,26 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.apache.nifi.controller.FlowController; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authentication.AuthenticationResponse; import org.apache.nifi.web.security.user.NiFiUserDetails; -import org.apache.nifi.web.security.x509.SubjectDnX509PrincipalExtractor; -import org.apache.nifi.web.security.x509.X509CertificateExtractor; import org.apache.nifi.user.NiFiUser; import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.security.token.NiFiAuthorizationToken; +import org.apache.nifi.web.security.x509.X509CertificateExtractor; +import org.apache.nifi.web.security.x509.X509IdentityProvider; import org.apache.nifi.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; -import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.filter.GenericFilterBean; /** - * Custom filter to extract a user's authorities from the request where the user - * was authenticated by the cluster manager and populate the threadlocal with - * the authorized user. If the request contains the appropriate header with - * authorities and the application instance is a node connected to the cluster, - * then the authentication/authorization steps remaining in the filter chain are - * skipped. + * Custom filter to extract a user's authorities from the request where the user was authenticated by the cluster manager and populate the threadlocal with the authorized user. If the request contains + * the appropriate header with authorities and the application instance is a node connected to the cluster, then the authentication/authorization steps remaining in the filter chain are skipped. * - * Checking if the application instance is a connected node is important because - * it prevents external clients from faking the request headers and bypassing - * the authentication processing chain. + * Checking if the application instance is a connected node is important because it prevents external clients from faking the request headers and bypassing the authentication processing chain. */ public class NodeAuthorizedUserFilter extends GenericFilterBean { @@ -62,9 +54,8 @@ public class NodeAuthorizedUserFilter extends GenericFilterBean { public static final String PROXY_USER_DETAILS = "X-ProxiedEntityUserDetails"; private NiFiProperties properties; - private final AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private final X509CertificateExtractor certificateExtractor = new X509CertificateExtractor(); - private final X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); + private X509CertificateExtractor certificateExtractor; + private X509IdentityProvider certificateIdentityProvider; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -83,16 +74,15 @@ public class NodeAuthorizedUserFilter extends GenericFilterBean { // check that we are connected to the cluster if (flowController.getNodeId() != null) { try { - // get the DN from the cert in the request - final X509Certificate certificate = certificateExtractor.extractClientCertificate((HttpServletRequest) request); + // attempt to extract the client certificate + final X509Certificate[] certificate = certificateExtractor.extractClientCertificate(httpServletRequest); if (certificate != null) { - // extract the principal from the certificate - final Object certificatePrincipal = principalExtractor.extractPrincipal(certificate); - final String dn = certificatePrincipal.toString(); + // authenticate the certificate + final AuthenticationResponse authenticationResponse = certificateIdentityProvider.authenticate(certificate); - // only consider the pre-authorized user when the request came from the NCM according to the DN in the certificate - final String clusterManagerDN = flowController.getClusterManagerDN(); - if (clusterManagerDN != null && clusterManagerDN.equals(dn)) { + // only consider the pre-authorized user when the request came directly from the NCM according to the DN in the certificate + final String clusterManagerIdentity = flowController.getClusterManagerDN(); + if (clusterManagerIdentity != null && clusterManagerIdentity.equals(authenticationResponse.getIdentity())) { // deserialize hex encoded object final Serializable userDetailsObj = WebUtils.deserializeHexToObject(hexEncodedUserDetails); @@ -102,18 +92,19 @@ public class NodeAuthorizedUserFilter extends GenericFilterBean { final NiFiUser user = userDetails.getNiFiUser(); // log the request attempt - response details will be logged later - logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", user.getDn(), httpServletRequest.getMethod(), + logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", user.getIdentity(), httpServletRequest.getMethod(), httpServletRequest.getRequestURL().toString(), request.getRemoteAddr())); - // we do not create the authentication token with the X509 certificate because the certificate is from the sending system, not the proxied user - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - token.setDetails(authenticationDetailsSource.buildDetails(request)); + // create the authorized nifi token + final NiFiAuthorizationToken token = new NiFiAuthorizationToken(userDetails); SecurityContextHolder.getContext().setAuthentication(token); } } } } catch (final ClassNotFoundException cnfe) { LOGGER.warn("Classpath issue detected because failed to deserialize authorized user in request header due to: " + cnfe, cnfe); + } catch (final IllegalArgumentException iae) { + // unable to authenticate a serialized user from the incoming request } } } @@ -121,8 +112,16 @@ public class NodeAuthorizedUserFilter extends GenericFilterBean { chain.doFilter(request, response); } - /* setters */ public void setProperties(NiFiProperties properties) { this.properties = properties; } + + public void setCertificateIdentityProvider(X509IdentityProvider certificateIdentityProvider) { + this.certificateIdentityProvider = certificateIdentityProvider; + } + + public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) { + this.certificateExtractor = certificateExtractor; + } + } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java new file mode 100644 index 0000000000..92a27aeff9 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java @@ -0,0 +1,312 @@ +/* + * 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.spring; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authentication.AuthenticationResponse; +import org.apache.nifi.authentication.LoginCredentials; +import org.apache.nifi.authentication.LoginIdentityProvider; +import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext; +import org.apache.nifi.authentication.LoginIdentityProviderInitializationContext; +import org.apache.nifi.authentication.LoginIdentityProviderLookup; +import org.apache.nifi.authentication.annotation.LoginIdentityProviderContext; +import org.apache.nifi.authentication.generated.LoginIdentityProviders; +import org.apache.nifi.authentication.generated.Property; +import org.apache.nifi.authentication.generated.Provider; +import org.apache.nifi.authorization.exception.ProviderCreationException; +import org.apache.nifi.authorization.exception.ProviderDestructionException; +import org.apache.nifi.nar.ExtensionManager; +import org.apache.nifi.nar.NarCloseable; +import org.apache.nifi.util.NiFiProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.xml.sax.SAXException; + +/** + * + */ +public class LoginIdentityProviderFactoryBean implements FactoryBean, DisposableBean, LoginIdentityProviderLookup { + + private static final Logger logger = LoggerFactory.getLogger(LoginIdentityProviderFactoryBean.class); + private static final String LOGIN_IDENTITY_PROVIDERS_XSD = "/login-identity-providers.xsd"; + private static final String JAXB_GENERATED_PATH = "org.apache.nifi.authentication.generated"; + private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext(); + + /** + * Load the JAXBContext. + */ + private static JAXBContext initializeJaxbContext() { + try { + return JAXBContext.newInstance(JAXB_GENERATED_PATH, LoginIdentityProviderFactoryBean.class.getClassLoader()); + } catch (JAXBException e) { + throw new RuntimeException("Unable to create JAXBContext."); + } + } + + private NiFiProperties properties; + private LoginIdentityProvider loginIdentityProvider; + private final Map loginIdentityProviders = new HashMap<>(); + + @Override + public LoginIdentityProvider getLoginIdentityProvider(String identifier) { + return loginIdentityProviders.get(identifier); + } + + @Override + public Object getObject() throws Exception { + if (loginIdentityProvider == null) { + // look up the login identity provider to use + final String loginIdentityProviderIdentifier = properties.getProperty(NiFiProperties.SECURITY_USER_LOGIN_IDENTITY_PROVIDER); + + // ensure the login identity provider class name was specified + if (StringUtils.isNotBlank(loginIdentityProviderIdentifier)) { + final LoginIdentityProviders loginIdentityProviderConfiguration = loadLoginIdentityProvidersConfiguration(); + + // create each login identity provider + for (final Provider provider : loginIdentityProviderConfiguration.getProvider()) { + loginIdentityProviders.put(provider.getIdentifier(), createLoginIdentityProvider(provider.getIdentifier(), provider.getClazz())); + } + + // configure each login identity provider + for (final Provider provider : loginIdentityProviderConfiguration.getProvider()) { + final LoginIdentityProvider instance = loginIdentityProviders.get(provider.getIdentifier()); + instance.onConfigured(loadLoginIdentityProviderConfiguration(provider)); + } + + // get the login identity provider instance + loginIdentityProvider = getLoginIdentityProvider(loginIdentityProviderIdentifier); + + // ensure it was found + if (loginIdentityProvider == null) { + throw new Exception(String.format("The specified login identity provider '%s' could not be found.", loginIdentityProviderIdentifier)); + } + } + } + + return loginIdentityProvider; + } + + private LoginIdentityProviders loadLoginIdentityProvidersConfiguration() throws Exception { + final File loginIdentityProvidersConfigurationFile = properties.getLoginIdentityProviderConfiguraitonFile(); + + // load the users from the specified file + if (loginIdentityProvidersConfigurationFile.exists()) { + try { + // find the schema + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + final Schema schema = schemaFactory.newSchema(LoginIdentityProviders.class.getResource(LOGIN_IDENTITY_PROVIDERS_XSD)); + + // attempt to unmarshal + final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(schema); + final JAXBElement element = unmarshaller.unmarshal(new StreamSource(loginIdentityProvidersConfigurationFile), LoginIdentityProviders.class); + return element.getValue(); + } catch (SAXException | JAXBException e) { + throw new Exception("Unable to load the login identity provider configuration file at: " + loginIdentityProvidersConfigurationFile.getAbsolutePath()); + } + } else { + throw new Exception("Unable to find the login identity provider configuration file at " + loginIdentityProvidersConfigurationFile.getAbsolutePath()); + } + } + + private LoginIdentityProvider createLoginIdentityProvider(final String identifier, final String loginIdentityProviderClassName) throws Exception { + // get the classloader for the specified login identity provider + final ClassLoader loginIdentityProviderClassLoader = ExtensionManager.getClassLoader(loginIdentityProviderClassName); + if (loginIdentityProviderClassLoader == null) { + throw new Exception(String.format("The specified login identity provider class '%s' is not known to this nifi.", loginIdentityProviderClassName)); + } + + // get the current context classloader + final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); + + final LoginIdentityProvider instance; + try { + // set the appropriate class loader + Thread.currentThread().setContextClassLoader(loginIdentityProviderClassLoader); + + // attempt to load the class + Class rawLoginIdentityProviderClass = Class.forName(loginIdentityProviderClassName, true, loginIdentityProviderClassLoader); + Class loginIdentityProviderClass = rawLoginIdentityProviderClass.asSubclass(LoginIdentityProvider.class); + + // otherwise create a new instance + Constructor constructor = loginIdentityProviderClass.getConstructor(); + instance = (LoginIdentityProvider) constructor.newInstance(); + + // method injection + performMethodInjection(instance, loginIdentityProviderClass); + + // field injection + performFieldInjection(instance, loginIdentityProviderClass); + + // call post construction lifecycle event + instance.initialize(new StandardLoginIdentityProviderInitializationContext(identifier, this)); + } finally { + if (currentClassLoader != null) { + Thread.currentThread().setContextClassLoader(currentClassLoader); + } + } + + return withNarLoader(instance); + } + + private LoginIdentityProviderConfigurationContext loadLoginIdentityProviderConfiguration(final Provider provider) { + final Map providerProperties = new HashMap<>(); + + for (final Property property : provider.getProperty()) { + providerProperties.put(property.getName(), property.getValue()); + } + + return new StandardLoginIdentityProviderConfigurationContext(provider.getIdentifier(), providerProperties); + } + + private void performMethodInjection(final LoginIdentityProvider instance, final Class loginIdentityProviderClass) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + + for (final Method method : loginIdentityProviderClass.getMethods()) { + if (method.isAnnotationPresent(LoginIdentityProviderContext.class)) { + // make the method accessible + final boolean isAccessible = method.isAccessible(); + method.setAccessible(true); + + try { + final Class[] argumentTypes = method.getParameterTypes(); + + // look for setters (single argument) + if (argumentTypes.length == 1) { + final Class argumentType = argumentTypes[0]; + + // look for well known types + if (NiFiProperties.class.isAssignableFrom(argumentType)) { + // nifi properties injection + method.invoke(instance, properties); + } + } + } finally { + method.setAccessible(isAccessible); + } + } + } + + final Class parentClass = loginIdentityProviderClass.getSuperclass(); + if (parentClass != null && LoginIdentityProvider.class.isAssignableFrom(parentClass)) { + performMethodInjection(instance, parentClass); + } + } + + private void performFieldInjection(final LoginIdentityProvider instance, final Class loginIdentityProviderClass) throws IllegalArgumentException, IllegalAccessException { + for (final Field field : loginIdentityProviderClass.getDeclaredFields()) { + if (field.isAnnotationPresent(LoginIdentityProviderContext.class)) { + // make the method accessible + final boolean isAccessible = field.isAccessible(); + field.setAccessible(true); + + try { + // get the type + final Class fieldType = field.getType(); + + // only consider this field if it isn't set yet + if (field.get(instance) == null) { + // look for well known types + if (NiFiProperties.class.isAssignableFrom(fieldType)) { + // nifi properties injection + field.set(instance, properties); + } + } + + } finally { + field.setAccessible(isAccessible); + } + } + } + + final Class parentClass = loginIdentityProviderClass.getSuperclass(); + if (parentClass != null && LoginIdentityProvider.class.isAssignableFrom(parentClass)) { + performFieldInjection(instance, parentClass); + } + } + + private LoginIdentityProvider withNarLoader(final LoginIdentityProvider baseProvider) { + return new LoginIdentityProvider() { + + @Override + public AuthenticationResponse authenticate(LoginCredentials credentials) { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + return baseProvider.authenticate(credentials); + } + } + + @Override + public void initialize(LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseProvider.initialize(initializationContext); + } + } + + @Override + public void onConfigured(LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseProvider.onConfigured(configurationContext); + } + } + + @Override + public void preDestruction() throws ProviderDestructionException { + try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { + baseProvider.preDestruction(); + } + } + }; + } + + @Override + public Class getObjectType() { + return LoginIdentityProvider.class; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public void destroy() throws Exception { + if (loginIdentityProvider != null) { + loginIdentityProvider.preDestruction(); + } + } + + public void setProperties(NiFiProperties properties) { + this.properties = properties; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/StandardLoginIdentityProviderConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/StandardLoginIdentityProviderConfigurationContext.java new file mode 100644 index 0000000000..5c662c7702 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/StandardLoginIdentityProviderConfigurationContext.java @@ -0,0 +1,51 @@ +/* + * 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.spring; + +import java.util.Collections; +import java.util.Map; +import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext; + +/** + * + */ +public class StandardLoginIdentityProviderConfigurationContext implements LoginIdentityProviderConfigurationContext { + + private final String identifier; + private final Map properties; + + public StandardLoginIdentityProviderConfigurationContext(String identifier, Map properties) { + this.identifier = identifier; + this.properties = properties; + } + + @Override + public String getIdentifier() { + return identifier; + } + + @Override + public Map getProperties() { + return Collections.unmodifiableMap(properties); + } + + @Override + public String getProperty(String property) { + return properties.get(property); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/StandardLoginIdentityProviderInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/StandardLoginIdentityProviderInitializationContext.java new file mode 100644 index 0000000000..af54df97ee --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/StandardLoginIdentityProviderInitializationContext.java @@ -0,0 +1,45 @@ +/* + * 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.spring; + +import org.apache.nifi.authentication.LoginIdentityProviderInitializationContext; +import org.apache.nifi.authentication.LoginIdentityProviderLookup; + +/** + * + */ +public class StandardLoginIdentityProviderInitializationContext implements LoginIdentityProviderInitializationContext { + + private final String identifier; + private final LoginIdentityProviderLookup lookup; + + public StandardLoginIdentityProviderInitializationContext(String identifier, final LoginIdentityProviderLookup lookup) { + this.identifier = identifier; + this.lookup = lookup; + } + + @Override + public String getIdentifier() { + return identifier; + } + + @Override + public LoginIdentityProviderLookup getAuthorityProviderLookup() { + return lookup; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java new file mode 100644 index 0000000000..35912398ce --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/LoginAuthenticationToken.java @@ -0,0 +1,123 @@ +/* + * 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.token; + +import org.apache.nifi.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(); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationRequestToken.java new file mode 100644 index 0000000000..6fee4ece93 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationRequestToken.java @@ -0,0 +1,40 @@ +/* + * 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.token; + +import org.apache.nifi.web.security.user.NewAccountRequest; + +/** + * This is an Authentication Token for a user that is requesting authentication in order to submit a new account request. + */ +public class NewAccountAuthenticationRequestToken extends NiFiAuthenticationRequestToken { + + final NewAccountRequest newAccountRequest; + + public NewAccountAuthenticationRequestToken(final NewAccountRequest newAccountRequest) { + super(newAccountRequest.getChain()); + this.newAccountRequest = newAccountRequest; + } + + public String getJustification() { + return newAccountRequest.getJustification(); + } + + public NewAccountRequest getNewAccountRequest() { + return newAccountRequest; + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationToken.java new file mode 100644 index 0000000000..5fe3a1dc4c --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NewAccountAuthenticationToken.java @@ -0,0 +1,46 @@ +/* + * 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.token; + +import org.apache.nifi.web.security.user.NewAccountRequest; +import org.springframework.security.authentication.AbstractAuthenticationToken; + +/** + * This is an Authentication Token for a user that has been authenticated but is not authorized to access the NiFi APIs. Typically, this authentication token is used successfully when requesting a + * NiFi account. Requesting any other endpoint would be rejected due to lack of roles. + */ +public class NewAccountAuthenticationToken extends AbstractAuthenticationToken { + + final NewAccountRequest newAccountRequest; + + public NewAccountAuthenticationToken(final NewAccountRequest newAccountRequest) { + super(null); + super.setAuthenticated(true); + this.newAccountRequest = newAccountRequest; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return newAccountRequest; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthenticationRequestToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthenticationRequestToken.java new file mode 100644 index 0000000000..3ae6491d08 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthenticationRequestToken.java @@ -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.web.security.token; + +import java.util.Collections; +import java.util.List; +import org.springframework.security.authentication.AbstractAuthenticationToken; + +/** + * An authentication token that is used as an authentication request. The request chain is specified during creation and is used authenticate the user(s). If the user is authenticated, the token is + * used to authorized the user(s). + */ +public class NiFiAuthenticationRequestToken extends AbstractAuthenticationToken { + + private final List chain; + + public NiFiAuthenticationRequestToken(final List chain) { + super(null); + this.chain = chain; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return chain; + } + + public List getChain() { + return Collections.unmodifiableList(chain); + } + + @Override + public final void setAuthenticated(boolean authenticated) { + throw new IllegalArgumentException("Cannot change the authenticated state."); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationToken.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationToken.java new file mode 100644 index 0000000000..0cb0353a53 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/token/NiFiAuthorizationToken.java @@ -0,0 +1,50 @@ +/* + * 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.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 NiFiAuthorizationToken extends AbstractAuthenticationToken { + + final UserDetails nifiUserDetails; + + public NiFiAuthorizationToken(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."); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NewAccountRequest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NewAccountRequest.java new file mode 100644 index 0000000000..3ec147a88e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NewAccountRequest.java @@ -0,0 +1,47 @@ +/* + * 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.user; + +import java.util.List; + +/** + * + */ +public class NewAccountRequest { + + private final List chain; + private final String justification; + + public NewAccountRequest(final List chain, final String justification) { + this.chain = chain; + this.justification = justification; + } + + public List getChain() { + return chain; + } + + public String getJustification() { + return justification; + } + + public String getUsername() { + // the end user is the first item in the chain + return chain.get(0); + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserDetails.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserDetails.java index c69b1e6e88..b559269a4d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserDetails.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserDetails.java @@ -73,10 +73,9 @@ public class NiFiUserDetails implements UserDetails { @Override public String getUsername() { - return user.getDn(); + return user.getIdentity(); } - // TODO: not sure how to handle these yet @Override public boolean isAccountNonExpired() { return true; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserUtils.java index bf1fe43838..341663e1cd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserUtils.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/user/NiFiUserUtils.java @@ -25,8 +25,7 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; /** - * Utility methods for retrieving information about the current application - * user. + * Utility methods for retrieving information about the current application user. * */ public final class NiFiUserUtils { @@ -58,8 +57,7 @@ public final class NiFiUserUtils { } /** - * Returns the current NiFiUser or null if the current user is not a - * NiFiUser. + * Returns the current NiFiUser or null if the current user is not a NiFiUser. * * @return user */ @@ -79,6 +77,27 @@ public final class NiFiUserUtils { return user; } + /** + * Returns the NewAccountRequest or null if this is not a new account request. + * + * @return new account request + */ + public static NewAccountRequest getNewAccountRequest() { + NewAccountRequest newAccountRequest = null; + + // obtain the principal in the current authentication + final SecurityContext context = SecurityContextHolder.getContext(); + final Authentication authentication = context.getAuthentication(); + if (authentication != null) { + Object principal = authentication.getPrincipal(); + if (principal instanceof NewAccountRequest) { + newAccountRequest = (NewAccountRequest) principal; + } + } + + return newAccountRequest; + } + public static String getNiFiUserName() { // get the nifi user to extract the username NiFiUser user = NiFiUserUtils.getNiFiUser(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java index 72baecb591..dd7d47e6b2 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationFilter.java @@ -16,302 +16,66 @@ */ package org.apache.nifi.web.security.x509; -import org.apache.nifi.web.security.x509.ocsp.OcspCertificateValidator; -import java.io.IOException; -import java.io.PrintWriter; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.nifi.admin.service.AdministrationException; -import org.apache.nifi.admin.service.UserService; -import org.apache.nifi.web.security.DnUtils; -import org.apache.nifi.web.security.UntrustedProxyException; -import org.apache.nifi.util.NiFiProperties; -import org.apache.commons.lang3.StringUtils; -import org.springframework.security.authentication.AccountStatusException; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; -import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; +import org.apache.nifi.authentication.AuthenticationResponse; +import org.apache.nifi.web.security.InvalidAuthenticationException; +import org.apache.nifi.web.security.NiFiAuthenticationFilter; +import org.apache.nifi.web.security.ProxiedEntitiesUtils; +import org.apache.nifi.web.security.token.NewAccountAuthenticationRequestToken; +import org.apache.nifi.web.security.token.NiFiAuthenticationRequestToken; +import org.apache.nifi.web.security.user.NewAccountRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Custom X509 filter that will inspect the HTTP headers for a proxied user - * before extracting the user details from the client certificate. + * 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 AbstractPreAuthenticatedProcessingFilter { +public class X509AuthenticationFilter extends NiFiAuthenticationFilter { - public static final String PROXY_ENTITIES_CHAIN = "X-ProxiedEntitiesChain"; - public static final String PROXY_ENTITIES_ACCEPTED = "X-ProxiedEntitiesAccepted"; - public static final String PROXY_ENTITIES_DETAILS = "X-ProxiedEntitiesDetails"; + private static final Logger logger = LoggerFactory.getLogger(X509AuthenticationFilter.class); - private final X509CertificateExtractor certificateExtractor = new X509CertificateExtractor(); - private final X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); - private OcspCertificateValidator certificateValidator; - private NiFiProperties properties; - private UserService userService; + private X509CertificateExtractor certificateExtractor; + private X509IdentityProvider certificateIdentityProvider; @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - final HttpServletResponse httpResponse = (HttpServletResponse) response; - - // determine if this request is attempting to create a new account - if (isNewAccountRequest((HttpServletRequest) request)) { - // determine if this nifi supports new account requests - if (properties.getSupportNewAccountRequests()) { - // ensure there is a certificate in the request - X509Certificate certificate = certificateExtractor.extractClientCertificate((HttpServletRequest) request); - if (certificate != null) { - // extract the principal from the certificate - Object certificatePrincipal = principalExtractor.extractPrincipal(certificate); - String principal = certificatePrincipal.toString(); - - // log the new user account request - logger.info("Requesting new user account for " + principal); - - try { - // get the justification - String justification = request.getParameter("justification"); - if (justification == null) { - justification = StringUtils.EMPTY; - } - - // create the pending user account - userService.createPendingUserAccount(principal, justification); - - // generate a response - httpResponse.setStatus(HttpServletResponse.SC_CREATED); - httpResponse.setContentType("text/plain"); - - // write the response message - PrintWriter out = response.getWriter(); - out.println("Not authorized. User account created. Authorization pending."); - } catch (IllegalArgumentException iae) { - handleUserServiceError((HttpServletRequest) request, httpResponse, HttpServletResponse.SC_BAD_REQUEST, iae.getMessage()); - } catch (AdministrationException ae) { - handleUserServiceError((HttpServletRequest) request, httpResponse, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ae.getMessage()); - } - } else { - // can this really happen? - handleMissingCertificate((HttpServletRequest) request, httpResponse); - } - } else { - handleUserServiceError((HttpServletRequest) request, httpResponse, HttpServletResponse.SC_NOT_FOUND, "This NiFi does not support new account requests."); - } - } else { - try { - // this not a request to create a user account - try to authorize - super.doFilter(request, response, chain); - } catch (AuthenticationException ae) { - // continue the filter chain since anonymous access should be supported - if (!properties.getNeedClientAuth()) { - chain.doFilter(request, response); - } else { - // create an appropriate response for the given exception - handleUnsuccessfulAuthentication((HttpServletRequest) request, httpResponse, ae); - } - } - } - } - - @Override - protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { - String principal; - - // extract the cert - X509Certificate certificate = certificateExtractor.extractClientCertificate(request); - - // ensure the cert was found - if (certificate == null) { + public NiFiAuthenticationRequestToken attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { + // only suppport x509 login when running securely + if (!request.isSecure()) { return null; } - // extract the principal - Object certificatePrincipal = principalExtractor.extractPrincipal(certificate); - principal = DnUtils.formatProxyDn(certificatePrincipal.toString()); + // look for a client certificate + final X509Certificate[] certificates = certificateExtractor.extractClientCertificate(request); + if (certificates == null) { + return null; + } + // attempt to authenticate if certificates were found + final AuthenticationResponse authenticationResponse; try { - // ensure the cert is valid - certificate.checkValidity(); - } 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); - } - return null; - } 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); - } - return null; + authenticationResponse = certificateIdentityProvider.authenticate(certificates); + } catch (final IllegalArgumentException iae) { + throw new InvalidAuthenticationException(iae.getMessage(), iae); } - // validate the certificate in question - try { - certificateValidator.validate(request); - } catch (final Exception e) { - logger.info(e.getMessage()); - if (logger.isDebugEnabled()) { - logger.debug("", e); - } - return null; - } - - // look for a proxied user - if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { - principal = request.getHeader(PROXY_ENTITIES_CHAIN) + principal; - } - - // log the request attempt - response details will be logged later - logger.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", principal, request.getMethod(), - request.getRequestURL().toString(), request.getRemoteAddr())); - - return principal; - } - - @Override - protected Object getPreAuthenticatedCredentials(HttpServletRequest request) { - return certificateExtractor.extractClientCertificate(request); - } - - @Override - protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) { - if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { - response.setHeader(PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString()); - } - super.successfulAuthentication(request, response, authResult); - } - - @Override - protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { - if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) { - response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage()); - } - super.unsuccessfulAuthentication(request, response, failed); - } - - /** - * Determines if the specified request is attempting to register a new user - * account. - * - * @param request http request - * @return true if new user - */ - private boolean isNewAccountRequest(HttpServletRequest request) { - if ("POST".equalsIgnoreCase(request.getMethod())) { - String path = request.getPathInfo(); - if (StringUtils.isNotBlank(path)) { - if ("/controller/users".equals(path)) { - return true; - } - } - } - return false; - } - - /** - * Handles requests that were unable to be authorized. - * - * @param request request - * @param response response - * @param ae ex - * @throws IOException ex - */ - private void handleUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException { - // set the response status - response.setContentType("text/plain"); - - // write the response message - PrintWriter out = response.getWriter(); - - // use the type of authentication exception to determine the response code - if (ae instanceof UsernameNotFoundException) { - if (properties.getSupportNewAccountRequests()) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - out.println("Not authorized."); - } else { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println("Access is denied."); - } - } else if (ae instanceof AccountStatusException) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println(ae.getMessage()); - } else if (ae instanceof UntrustedProxyException) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println(ae.getMessage()); - } else if (ae instanceof AuthenticationServiceException) { - logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - out.println(String.format("Unable to authorize: %s", ae.getMessage())); + final List proxyChain = ProxiedEntitiesUtils.buildProxiedEntitiesChain(request, authenticationResponse.getIdentity()); + if (isNewAccountRequest(request)) { + return new NewAccountAuthenticationRequestToken(new NewAccountRequest(proxyChain, getJustification(request))); } else { - logger.error(String.format("Unable to authorize: %s", ae.getMessage()), ae); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - out.println("Access is denied."); + return new NiFiAuthenticationRequestToken(proxyChain); } - - // log the failure - logger.info(String.format("Rejecting access to web api: %s", ae.getMessage())); - - // optionally log the stack trace - if (logger.isDebugEnabled()) { - logger.debug(StringUtils.EMPTY, ae); - } - } - - private void handleUserServiceError(HttpServletRequest request, HttpServletResponse response, int responseCode, String message) throws IOException { - // set the response status - response.setContentType("text/plain"); - response.setStatus(responseCode); - - // write the response message - PrintWriter out = response.getWriter(); - out.println(message); - - // log the failure - logger.info(String.format("Unable to process request because %s", message)); - } - - /** - * Handles requests that failed because they were bad input. - * - * @param request request - * @param response response - * @throws IOException ioe - */ - private void handleMissingCertificate(HttpServletRequest request, HttpServletResponse response) throws IOException { - // set the response status - response.setContentType("text/plain"); - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - - // write the response message - PrintWriter out = response.getWriter(); - out.println("Unable to process request because the user certificate was not specified."); - - // log the failure - logger.info("Unable to process request because the user certificate was not specified."); } /* setters */ - public void setProperties(NiFiProperties properties) { - this.properties = properties; + public void setCertificateExtractor(X509CertificateExtractor certificateExtractor) { + this.certificateExtractor = certificateExtractor; } - public void setUserService(UserService userService) { - this.userService = userService; - } - - public void setCertificateValidator(OcspCertificateValidator certificateValidator) { - this.certificateValidator = certificateValidator; + public void setCertificateIdentityProvider(X509IdentityProvider certificateIdentityProvider) { + this.certificateIdentityProvider = certificateIdentityProvider; } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509CertificateExtractor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509CertificateExtractor.java index b40d5a5dbe..98d0154b2a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509CertificateExtractor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509CertificateExtractor.java @@ -35,11 +35,11 @@ public class X509CertificateExtractor { * @param request http request * @return cert */ - public X509Certificate extractClientCertificate(HttpServletRequest request) { + public X509Certificate[] extractClientCertificate(HttpServletRequest request) { X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); if (certs != null && certs.length > 0) { - return certs[0]; + return certs; } if (logger.isDebugEnabled()) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509CertificateValidator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509CertificateValidator.java new file mode 100644 index 0000000000..cb56a11ebe --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509CertificateValidator.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.web.security.x509; + +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import org.apache.nifi.web.security.x509.ocsp.CertificateStatusException; +import org.apache.nifi.web.security.x509.ocsp.OcspCertificateValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Extracts client certificates from Http requests. + */ +public class X509CertificateValidator { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private OcspCertificateValidator ocspValidator; + + /** + * Extract the client certificate from the specified HttpServletRequest or null if none is specified. + * + * @param certificates the client certificates + * @throws java.security.cert.CertificateExpiredException cert is expired + * @throws java.security.cert.CertificateNotYetValidException cert is not yet valid + * @throws org.apache.nifi.web.security.x509.ocsp.CertificateStatusException ocsp validation issue + */ + public void validateClientCertificate(final X509Certificate[] certificates) + throws CertificateExpiredException, CertificateNotYetValidException, CertificateStatusException { + + // ensure the cert is valid + certificates[0].checkValidity(); + + // perform ocsp validator if necessary + ocspValidator.validate(certificates); + } + + public void setOcspValidator(OcspCertificateValidator ocspValidator) { + this.ocspValidator = ocspValidator; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509IdentityProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509IdentityProvider.java new file mode 100644 index 0000000000..db0b52904f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509IdentityProvider.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.web.security.x509; + +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.concurrent.TimeUnit; +import org.apache.nifi.authentication.AuthenticationResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; + +/** + * Identity provider for extract the authenticating a ServletRequest with a X509Certificate. + */ +public class X509IdentityProvider { + + private static final Logger logger = LoggerFactory.getLogger(X509IdentityProvider.class); + + private final String issuer = getClass().getSimpleName(); + + private X509CertificateValidator certificateValidator; + private X509PrincipalExtractor principalExtractor; + + /** + * Authenticates the specified request by checking certificate validity. + * + * @param certificates the client certificates + * @return an authentication response + * @throws IllegalArgumentException the request did not contain a valid certificate (or no certificate) + */ + public AuthenticationResponse authenticate(final X509Certificate[] certificates) throws IllegalArgumentException { + // ensure the cert was found + if (certificates == null || certificates.length == 0) { + throw new IllegalArgumentException("The specified request does not contain a client certificate."); + } + + // extract the principal + final Object certificatePrincipal = principalExtractor.extractPrincipal(certificates[0]); + final String principal = certificatePrincipal.toString(); + + try { + certificateValidator.validateClientCertificate(certificates); + } 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); + } 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); + } catch (final Exception e) { + logger.info(e.getMessage()); + if (logger.isDebugEnabled()) { + logger.debug("", e); + } + throw new IllegalArgumentException(e.getMessage(), e); + } + + // build the authentication response + return new AuthenticationResponse(principal, principal, TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS), issuer); + } + + /* setters */ + public void setCertificateValidator(X509CertificateValidator certificateValidator) { + this.certificateValidator = certificateValidator; + } + + public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) { + this.principalExtractor = principalExtractor; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidator.java index 81e4bd6521..832a63c36d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidator.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidator.java @@ -42,7 +42,6 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; -import javax.servlet.http.HttpServletRequest; import org.apache.nifi.framework.security.util.SslContextFactory; import org.apache.nifi.web.security.x509.ocsp.OcspStatus.ValidationStatus; import org.apache.nifi.web.security.x509.ocsp.OcspStatus.VerificationStatus; @@ -158,8 +157,7 @@ public class OcspCertificateValidator { } /** - * Loads the trusted certificate authorities according to the specified - * properties. + * Loads the trusted certificate authorities according to the specified properties. * * @param properties properties * @return map of certificate authorities @@ -208,12 +206,10 @@ public class OcspCertificateValidator { /** * Validates the specified certificate using OCSP if configured. * - * @param request http request + * @param certificates the client certificates * @throws CertificateStatusException ex */ - public void validate(final HttpServletRequest request) throws CertificateStatusException { - final X509Certificate[] certificates = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); - + public void validate(final X509Certificate[] certificates) throws CertificateStatusException { // only validate if configured to do so if (client != null && certificates != null && certificates.length > 0) { final X509Certificate subjectCertificate = getSubjectCertificate(certificates); @@ -395,13 +391,9 @@ public class OcspCertificateValidator { } /** - * Gets the trusted responder certificate. The response contains the - * responder certificate, however we cannot blindly trust it. Instead, we - * use a configured trusted CA. If the responder certificate is a trusted - * CA, then we can use it. If the responder certificate is not directly - * trusted, we still may be able to trust it if it was issued by the same CA - * that issued the subject certificate. Other various checks may be required - * (this portion is currently not implemented). + * Gets the trusted responder certificate. The response contains the responder certificate, however we cannot blindly trust it. Instead, we use a configured trusted CA. If the responder + * certificate is a trusted CA, then we can use it. If the responder certificate is not directly trusted, we still may be able to trust it if it was issued by the same CA that issued the subject + * certificate. Other various checks may be required (this portion is currently not implemented). * * @param responderCertificate cert * @param issuerCertificate cert diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml index fa5b9e296c..40f678cb26 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml @@ -13,70 +13,45 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"> - - - - - - - + + - - - - - + + - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - + + + + - - - + + + + + + + + + + - - - - + + + + + + + + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/xsd/login-identity-providers.xsd b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/xsd/login-identity-providers.xsd new file mode 100644 index 0000000000..628f390f07 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/xsd/login-identity-providers.xsd @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java index 6d0c3cb24a..545655252a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/authorization/NiFiAuthorizationServiceTest.java @@ -16,6 +16,7 @@ */ package org.apache.nifi.web.security.authorization; +import java.util.Arrays; import org.apache.nifi.admin.service.AccountDisabledException; import org.apache.nifi.admin.service.AccountNotFoundException; import org.apache.nifi.admin.service.AccountPendingException; @@ -24,8 +25,8 @@ 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.token.NiFiAuthenticationRequestToken; import org.apache.nifi.web.security.user.NiFiUserDetails; import org.junit.Assert; import org.junit.Before; @@ -66,10 +67,10 @@ public class NiFiAuthorizationServiceTest { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); - String dn = (String) args[0]; + String identity = (String) args[0]; - if (null != dn) { - switch (dn) { + if (null != identity) { + switch (identity) { case USER_NOT_FOUND: case PROXY_NOT_FOUND: throw new AccountNotFoundException(""); @@ -81,13 +82,13 @@ public class NiFiAuthorizationServiceTest { throw new AdministrationException(); case USER: final NiFiUser monitor = new NiFiUser(); - monitor.setDn(dn); + monitor.setIdentity(identity); monitor.getAuthorities().add(Authority.ROLE_MONITOR); return monitor; case PROXY: case PROXY_PROXY: final NiFiUser proxy = new NiFiUser(); - proxy.setDn(dn); + proxy.setIdentity(identity); proxy.getAuthorities().add(Authority.ROLE_PROXY); return proxy; } @@ -103,15 +104,18 @@ public class NiFiAuthorizationServiceTest { authorizationService.setUserService(userService); } + private NiFiAuthenticationRequestToken createRequestAuthentication(final String... identities) { + return new NiFiAuthenticationRequestToken(Arrays.asList(identities)); + } + /** - * Ensures the authorization service correctly handles users invalid dn - * chain. + * Ensures the authorization service correctly handles users invalid identity chain. * * @throws Exception ex */ @Test(expected = UntrustedProxyException.class) public void testInvalidDnChain() throws Exception { - authorizationService.loadUserByUsername(USER); + authorizationService.loadUserDetails(createRequestAuthentication()); } /** @@ -121,7 +125,7 @@ public class NiFiAuthorizationServiceTest { */ @Test(expected = UsernameNotFoundException.class) public void testAccountNotFound() throws Exception { - authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER_NOT_FOUND)); + authorizationService.loadUserDetails(createRequestAuthentication(USER_NOT_FOUND)); } /** @@ -131,7 +135,7 @@ public class NiFiAuthorizationServiceTest { */ @Test(expected = AccountStatusException.class) public void testAccountDisabled() throws Exception { - authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER_DISABLED)); + authorizationService.loadUserDetails(createRequestAuthentication(USER_DISABLED)); } /** @@ -141,18 +145,17 @@ public class NiFiAuthorizationServiceTest { */ @Test(expected = AccountStatusException.class) public void testAccountPending() throws Exception { - authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER_PENDING)); + authorizationService.loadUserDetails(createRequestAuthentication(USER_PENDING)); } /** - * Ensures the authorization service correctly handles account - * administration exception. + * Ensures the authorization service correctly handles account administration exception. * * @throws Exception ex */ @Test(expected = AuthenticationServiceException.class) public void testAccountAdminException() throws Exception { - authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER_ADMIN_EXCEPTION)); + authorizationService.loadUserDetails(createRequestAuthentication(USER_ADMIN_EXCEPTION)); } /** @@ -162,10 +165,10 @@ public class NiFiAuthorizationServiceTest { */ @Test public void testNoProxy() throws Exception { - final NiFiUserDetails details = (NiFiUserDetails) authorizationService.loadUserByUsername(DnUtils.formatProxyDn(USER)); + final NiFiUserDetails details = (NiFiUserDetails) authorizationService.loadUserDetails(createRequestAuthentication(USER)); final NiFiUser user = details.getNiFiUser(); - Assert.assertEquals(USER, user.getDn()); + Assert.assertEquals(USER, user.getIdentity()); Assert.assertNull(user.getChain()); } @@ -176,21 +179,18 @@ public class NiFiAuthorizationServiceTest { */ @Test(expected = UntrustedProxyException.class) public void testInvalidProxy() throws Exception { - final String dnChain = DnUtils.formatProxyDn(USER) + DnUtils.formatProxyDn(USER); - authorizationService.loadUserByUsername(dnChain); + authorizationService.loadUserDetails(createRequestAuthentication(USER, USER)); } /** - * Ensures the authorization service correctly handles proxy not found by - * attempting to create an account request for the proxy. + * Ensures the authorization service correctly handles proxy not found by attempting to create an account request for the proxy. * * @throws Exception ex */ - @Test(expected = UsernameNotFoundException.class) + @Test(expected = UntrustedProxyException.class) public void testProxyNotFound() throws Exception { try { - final String dnChain = DnUtils.formatProxyDn(USER) + DnUtils.formatProxyDn(PROXY_NOT_FOUND); - authorizationService.loadUserByUsername(DnUtils.formatProxyDn(dnChain)); + authorizationService.loadUserDetails(createRequestAuthentication(USER, PROXY_NOT_FOUND)); } finally { Mockito.verify(userService).createPendingUserAccount(Mockito.eq(PROXY_NOT_FOUND), Mockito.anyString()); } @@ -203,19 +203,18 @@ public class NiFiAuthorizationServiceTest { */ @Test public void testProxy() throws Exception { - final String dnChain = DnUtils.formatProxyDn(USER) + DnUtils.formatProxyDn(PROXY); - final NiFiUserDetails details = (NiFiUserDetails) authorizationService.loadUserByUsername(dnChain); + final NiFiUserDetails details = (NiFiUserDetails) authorizationService.loadUserDetails(createRequestAuthentication(USER, PROXY)); final NiFiUser user = details.getNiFiUser(); // verify the user - Assert.assertEquals(USER, user.getDn()); + Assert.assertEquals(USER, user.getIdentity()); Assert.assertNotNull(user.getChain()); // get the proxy final NiFiUser proxy = user.getChain(); // verify the proxy - Assert.assertEquals(PROXY, proxy.getDn()); + Assert.assertEquals(PROXY, proxy.getIdentity()); Assert.assertNull(proxy.getChain()); } @@ -226,26 +225,25 @@ public class NiFiAuthorizationServiceTest { */ @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 NiFiUserDetails details = (NiFiUserDetails) authorizationService.loadUserDetails(createRequestAuthentication(USER, PROXY, PROXY_PROXY)); final NiFiUser user = details.getNiFiUser(); // verify the user - Assert.assertEquals(USER, user.getDn()); + Assert.assertEquals(USER, user.getIdentity()); Assert.assertNotNull(user.getChain()); // get the proxy NiFiUser proxy = user.getChain(); // verify the proxy - Assert.assertEquals(PROXY, proxy.getDn()); + Assert.assertEquals(PROXY, proxy.getIdentity()); Assert.assertNotNull(proxy.getChain()); // get the proxies proxy proxy = proxy.getChain(); // verify the proxies proxy - Assert.assertEquals(PROXY_PROXY, proxy.getDn()); + Assert.assertEquals(PROXY_PROXY, proxy.getIdentity()); Assert.assertNull(proxy.getChain()); } } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java new file mode 100644 index 0000000000..59c66eff9a --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/jwt/JwtServiceTest.java @@ -0,0 +1,445 @@ +/* + * 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.jwt; + +import io.jsonwebtoken.JwtException; +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.codec.binary.Base64; +import org.apache.nifi.admin.service.AdministrationException; +import org.apache.nifi.admin.service.KeyService; +import org.apache.nifi.key.Key; +import org.apache.nifi.web.security.token.LoginAuthenticationToken; +import org.codehaus.jettison.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.when; + +public class JwtServiceTest { + + private static final Logger logger = LoggerFactory.getLogger(JwtServiceTest.class); + + /** + * These constant strings were generated using the tool at http://jwt.io + */ + private static final String VALID_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRl" + + "ciIsImF1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZ" + + "XJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoxLCJleHAiOjI0NDc4MDg3NjEsIm" + + "lhdCI6MTQ0NzgwODcwMX0.r6aGZ6FNNYMOpcXW8BK2VYaQeX1uO0Aw1KJfjB3Q1DU"; + + // This token has an empty subject field + private static final String INVALID_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZG" + + "VudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvI" + + "iwia2lkIjoxLCJleHAiOjI0NDc4MDg3NjEsImlhdCI6MTQ0NzgwODcwMX0" + + ".x_1p2M6E0vwWHWMujIUnSL3GkFoDqqICllRxo2SMNaw"; + + private static final String VALID_UNSIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZC" + + "I6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJl" + + "c3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9"; + + // This token has an empty subject field + private static final String INVALID_UNSIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVu" + + "dGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoi" + + "YWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9"; + + // Algorithm field is "none" + private static final String VALID_MALSIGNED_TOKEN = "eyJhbGciOiJub25lIn0" + + ".eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZC" + + "I6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJl" + + "c3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoxNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9" + + ".mPO_wMNMl_zjMNevhNvUoXbSJ9Kx6jAe5OxDIAzKQbI"; + + // Algorithm field is "none" and no signature is present + private static final String VALID_MALSIGNED_NO_SIG_TOKEN = "eyJhbGciOiJub25lIn0" + + ".eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY" + + "2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIj" + + "oiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9."; + + // This token has an empty subject field + private static final String INVALID_MALSIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik1vY2tJZGVud" + + "Gl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvcHJlc3RvIiwia2lkIjoiYW" + + "xvcHJlc3RvIiwiZXhwIjoxNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.WAwmUY4KHKV2oARNodkqDkbZsfRXGZfD2Ccy64GX9QF"; + + // This token is signed but expired + private static final String EXPIRED_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiIiLCJpc3MiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsImF1ZCI6Ik" + + "1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxvc" + + "HJlc3RvIiwia2lkIjoxLCJleHAiOjE0NDc4MDg3NjEsImlhdCI6MTQ0NzgwODcw" + + "MX0.ZPDIhNKuL89vTGXcuztOYaGifwcrQy_gid4j8Sspmto"; + + // Subject is "mgilman" but signed with "alopresto" key + private static final String IMPOSTER_SIGNED_TOKEN = "eyJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiJtZ2lsbWFuIiwiaXNzIjoiTW9ja0lkZW50aXR5UHJvdmlkZXIiLCJ" + + "hdWQiOiJNb2NrSWRlbnRpdHlQcm92aWRlciIsInByZWZlcnJlZF91c2VybmFtZSI" + + "6ImFsb3ByZXN0byIsImtpZCI6MSwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc" + + "4MDg3MDF9.aw5OAvLTnb_sHmSQOQzW-A7NImiZgXJ2ngbbNL2Ymkc"; + + // Issuer field is set to unknown provider + private static final String UNKNOWN_ISSUER_TOKEN = "eyJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiJhbG9wcmVzdG8iLCJpc3MiOiJVbmtub3duSWRlbnRpdHlQcm92aWRlciIsIm" + + "F1ZCI6Ik1vY2tJZGVudGl0eVByb3ZpZGVyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWxv" + + "cHJlc3RvIiwia2lkIjoiYWxvcHJlc3RvIiwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9" + + ".SAd9tyNwSaijWet9wvAWSNmpxmPSK4XQuLx7h3ARqBo"; + + // Issuer field is absent + private static final String NO_ISSUER_TOKEN = "eyJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiJhbG9wcmVzdG8iLCJhdWQiOiJNb2NrSWRlbnRpdHlQcm92a" + + "WRlciIsInByZWZlcnJlZF91c2VybmFtZSI6ImFsb3ByZXN0byIsImtpZCI" + + "6MSwiZXhwIjoyNDQ3ODA4NzYxLCJpYXQiOjE0NDc4MDg3MDF9.6kDjDanA" + + "g0NQDb3C8FmgbBAYDoIfMAEkF4WMVALsbJA"; + + private static final String DEFAULT_HEADER = "{\"alg\":\"HS256\"}"; + private static final String DEFAULT_IDENTITY = "alopresto"; + + private static final String TOKEN_DELIMITER = "."; + + private static final String HMAC_SECRET = "test_hmac_shared_secret"; + + private KeyService mockKeyService; + + // Class under test + private JwtService jwtService; + + private String generateHS256Token(String rawHeader, String rawPayload, boolean isValid, boolean isSigned) { + return generateHS256Token(rawHeader, rawPayload, HMAC_SECRET, isValid, isSigned); + } + + private String generateHS256Token(String rawHeader, String rawPayload, String hmacSecret, boolean isValid, + boolean isSigned) { + try { + logger.info("Generating token for " + rawHeader + " + " + rawPayload); + + String base64Header = Base64.encodeBase64URLSafeString(rawHeader.getBytes(CharEncoding.UTF_8)); + String base64Payload = Base64.encodeBase64URLSafeString(rawPayload.getBytes(CharEncoding.UTF_8)); + // TODO: Support valid/invalid manipulation + + final String body = base64Header + TOKEN_DELIMITER + base64Payload; + + String signature = generateHMAC(hmacSecret, body); + + return body + TOKEN_DELIMITER + signature; + } catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) { + final String errorMessage = "Could not generate the token"; + logger.error(errorMessage, e); + fail(errorMessage); + return null; + } + } + + private String generateHMAC(String hmacSecret, String body) throws NoSuchAlgorithmException, + UnsupportedEncodingException, InvalidKeyException { + Mac hmacSHA256 = Mac.getInstance("HmacSHA256"); + SecretKeySpec secret_key = new SecretKeySpec(hmacSecret.getBytes("UTF-8"), "HmacSHA256"); + hmacSHA256.init(secret_key); + return Base64.encodeBase64URLSafeString(hmacSHA256.doFinal(body.getBytes("UTF-8"))); + } + + @Before + public void setUp() throws Exception { + final Key key = new Key(); + key.setId(1); + key.setIdentity(DEFAULT_IDENTITY); + key.setKey(HMAC_SECRET); + + mockKeyService = Mockito.mock(KeyService.class); + when(mockKeyService.getKey(anyInt())).thenReturn(key); + when(mockKeyService.getOrCreateKey(anyString())).thenReturn(key); + jwtService = new JwtService(mockKeyService); + } + + @After + public void tearDown() throws Exception { + + } + + @Test + public void testShouldGetAuthenticationForValidToken() throws Exception { + // Arrange + String token = VALID_SIGNED_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + assertEquals("Identity", DEFAULT_IDENTITY, identity); + } + + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForInvalidToken() throws Exception { + // Arrange + String token = INVALID_SIGNED_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForEmptyToken() throws Exception { + // Arrange + String token = ""; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForUnsignedToken() throws Exception { + // Arrange + String token = VALID_UNSIGNED_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForMalsignedToken() throws Exception { + // Arrange + String token = VALID_MALSIGNED_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForTokenWithWrongAlgorithm() throws Exception { + // Arrange + String token = VALID_MALSIGNED_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForTokenWithWrongAlgorithmAndNoSignature() throws Exception { + // Arrange + String token = VALID_MALSIGNED_NO_SIG_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Ignore("Not yet implemented") + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForTokenFromUnknownIdentityProvider() throws Exception { + // Arrange + String token = UNKNOWN_ISSUER_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForTokenFromEmptyIdentityProvider() throws Exception { + // Arrange + String token = NO_ISSUER_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForExpiredToken() throws Exception { + // Arrange + String token = EXPIRED_SIGNED_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Test(expected = JwtException.class) + public void testShouldNotGetAuthenticationForImposterToken() throws Exception { + // Arrange + String token = IMPOSTER_SIGNED_TOKEN; + + // Act + String identity = jwtService.getAuthenticationFromToken(token); + logger.debug("Extracted identity: " + identity); + + // Assert + // Should fail + } + + @Test + public void testShouldGenerateSignedToken() throws Exception { + // Arrange + + // Token expires in 60 seconds + final int EXPIRATION_MILLIS = 60000; + LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken("alopresto", + EXPIRATION_MILLIS, + "MockIdentityProvider"); + logger.debug("Generating token for " + loginAuthenticationToken); + + final String EXPECTED_HEADER = DEFAULT_HEADER; + + // Convert the expiration time from ms to s + final long TOKEN_EXPIRATION_SEC = (long) (loginAuthenticationToken.getExpiration() / 1000.0); + + // Act + String token = jwtService.generateSignedToken(loginAuthenticationToken); + logger.debug("Generated JWT: " + token); + + // Run after the SUT generates the token to ensure the same issued at time + // Split the token, decode the middle section, and form a new String + final String DECODED_PAYLOAD = new String(Base64.decodeBase64(token.split("\\.")[1].getBytes())); + final long ISSUED_AT_SEC = Long.valueOf(DECODED_PAYLOAD.substring(DECODED_PAYLOAD.lastIndexOf(":") + 1, + DECODED_PAYLOAD.length() - 1)); + logger.trace("Actual token was issued at " + ISSUED_AT_SEC); + + // Always use LinkedHashMap to enforce order of the keys because the signature depends on order + Map claims = new LinkedHashMap<>(); + claims.put("sub", "alopresto"); + claims.put("iss", "MockIdentityProvider"); + claims.put("aud", "MockIdentityProvider"); + claims.put("preferred_username", "alopresto"); + claims.put("kid", 1); + claims.put("exp", TOKEN_EXPIRATION_SEC); + claims.put("iat", ISSUED_AT_SEC); + logger.trace("JSON Object to String: " + new JSONObject(claims).toString()); + + final String EXPECTED_PAYLOAD = new JSONObject(claims).toString(); + final String EXPECTED_TOKEN_STRING = generateHS256Token(EXPECTED_HEADER, EXPECTED_PAYLOAD, true, true); + logger.debug("Expected JWT: " + EXPECTED_TOKEN_STRING); + + // Assert + assertEquals("JWT token", EXPECTED_TOKEN_STRING, token); + } + + @Test(expected = IllegalArgumentException.class) + public void testShouldNotGenerateTokenWithNullAuthenticationToken() throws Exception { + // Arrange + LoginAuthenticationToken nullLoginAuthenticationToken = null; + logger.debug("Generating token for " + nullLoginAuthenticationToken); + + // Act + jwtService.generateSignedToken(nullLoginAuthenticationToken); + + // Assert + // Should throw exception + } + + @Test(expected = JwtException.class) + public void testShouldNotGenerateTokenWithEmptyIdentity() throws Exception { + // Arrange + final int EXPIRATION_MILLIS = 60000; + LoginAuthenticationToken emptyIdentityLoginAuthenticationToken = new LoginAuthenticationToken("", + EXPIRATION_MILLIS, "MockIdentityProvider"); + logger.debug("Generating token for " + emptyIdentityLoginAuthenticationToken); + + // Act + jwtService.generateSignedToken(emptyIdentityLoginAuthenticationToken); + + // Assert + // Should throw exception + } + + @Test(expected = JwtException.class) + public void testShouldNotGenerateTokenWithNullIdentity() throws Exception { + // Arrange + final int EXPIRATION_MILLIS = 60000; + LoginAuthenticationToken nullIdentityLoginAuthenticationToken = new LoginAuthenticationToken(null, + EXPIRATION_MILLIS, "MockIdentityProvider"); + logger.debug("Generating token for " + nullIdentityLoginAuthenticationToken); + + // Act + jwtService.generateSignedToken(nullIdentityLoginAuthenticationToken); + + // Assert + // Should throw exception + } + + @Test(expected = JwtException.class) + public void testShouldNotGenerateTokenWithMissingKey() throws Exception { + // Arrange + final int EXPIRATION_MILLIS = 60000; + LoginAuthenticationToken loginAuthenticationToken = new LoginAuthenticationToken("alopresto", + EXPIRATION_MILLIS, + "MockIdentityProvider"); + logger.debug("Generating token for " + loginAuthenticationToken); + + // Set up the bad key service + KeyService missingKeyService = Mockito.mock(KeyService.class); + when(missingKeyService.getOrCreateKey(anyString())).thenThrow(new AdministrationException("Could not find a " + + "key for that user")); + jwtService = new JwtService(missingKeyService); + + // Act + jwtService.generateSignedToken(loginAuthenticationToken); + + // Assert + // Should throw exception + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/resources/logback-test.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..fe5d27efa0 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/resources/logback-test.xml @@ -0,0 +1,36 @@ + + + + + + + %-4r [%t] %-5p %c - %m%n + + + + + ./target/log + + %date %level [%thread] %logger{40} %msg%n + + + + + + + + + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml index 195f14bc08..a4ef51664d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml @@ -32,6 +32,7 @@ templates.properties users.properties bulletin-board.properties + login.properties provenance.properties @@ -54,6 +55,7 @@ src/main/resources/filters/${templates.filter} src/main/resources/filters/${users.filter} src/main/resources/filters/${bulletin.board.filter} + src/main/resources/filters/${login.filter} src/main/resources/filters/${provenance.filter} @@ -89,7 +91,8 @@ **/cluster.jsp, **/templates.jsp, **/users.jsp, - **/bulletin-board.jsp + **/bulletin-board.jsp, + **/login.jsp @@ -209,6 +212,14 @@ true + + src/main/webapp/WEB-INF/pages + WEB-INF/pages + + login.jsp + + true + @@ -229,6 +240,7 @@ templates-min.properties users-min.properties bulletin-board-min.properties + login-min.properties provenance-min.properties @@ -263,11 +275,10 @@ ${staging.dir}/js/nf/canvas/nf-canvas-utils.js ${staging.dir}/js/nf/nf-dialog.js ${staging.dir}/js/nf/nf-shell.js - ${staging.dir}/js/nf/canvas/nf-storage.js + ${staging.dir}/js/nf/nf-storage.js ${staging.dir}/js/nf/canvas/nf-snippet.js ${staging.dir}/js/nf/canvas/nf-canvas-toolbox.js ${staging.dir}/js/nf/canvas/nf-custom-ui.js - ${staging.dir}/js/nf/canvas/nf-registration.js ${staging.dir}/js/nf/canvas/nf-controller-service.js ${staging.dir}/js/nf/canvas/nf-reporting-task.js ${staging.dir}/js/nf/canvas/nf-processor-configuration.js @@ -317,6 +328,7 @@ ${staging.dir}/js/nf/nf-client.js ${staging.dir}/js/nf/nf-common.js ${staging.dir}/js/nf/nf-dialog.js + ${staging.dir}/js/nf/nf-storage.js ${staging.dir}/js/nf/history/nf-history.js ${staging.dir}/js/nf/history/nf-history-table.js ${staging.dir}/js/nf/history/nf-history-model.js @@ -329,6 +341,7 @@ ${staging.dir}/js/nf/nf-client.js ${staging.dir}/js/nf/nf-common.js ${staging.dir}/js/nf/nf-dialog.js + ${staging.dir}/js/nf/nf-storage.js ${staging.dir}/js/nf/provenance/nf-provenance.js ${staging.dir}/js/nf/provenance/nf-provenance-table.js @@ -340,6 +353,7 @@ ${staging.dir}/js/nf/nf-client.js ${staging.dir}/js/nf/nf-common.js ${staging.dir}/js/nf/nf-dialog.js + ${staging.dir}/js/nf/nf-storage.js ${staging.dir}/js/nf/nf-processor-details.js ${staging.dir}/js/nf/nf-connection-details.js ${staging.dir}/js/nf/summary/nf-summary.js @@ -354,6 +368,7 @@ ${staging.dir}/js/nf/nf-client.js ${staging.dir}/js/nf/nf-common.js ${staging.dir}/js/nf/nf-dialog.js + ${staging.dir}/js/nf/nf-storage.js ${staging.dir}/js/nf/counters/nf-counters.js ${staging.dir}/js/nf/counters/nf-counters-table.js @@ -365,6 +380,7 @@ ${staging.dir}/js/nf/nf-client.js ${staging.dir}/js/nf/nf-common.js ${staging.dir}/js/nf/nf-dialog.js + ${staging.dir}/js/nf/nf-storage.js ${staging.dir}/js/nf/templates/nf-templates.js ${staging.dir}/js/nf/templates/nf-templates-table.js @@ -376,6 +392,7 @@ ${staging.dir}/js/nf/nf-client.js ${staging.dir}/js/nf/nf-common.js ${staging.dir}/js/nf/nf-dialog.js + ${staging.dir}/js/nf/nf-storage.js ${staging.dir}/js/nf/cluster/nf-cluster.js ${staging.dir}/js/nf/cluster/nf-cluster-table.js @@ -387,6 +404,7 @@ ${staging.dir}/js/nf/nf-client.js ${staging.dir}/js/nf/nf-common.js ${staging.dir}/js/nf/nf-dialog.js + ${staging.dir}/js/nf/nf-storage.js ${staging.dir}/js/nf/users/nf-users.js ${staging.dir}/js/nf/users/nf-users-table.js @@ -398,9 +416,21 @@ ${staging.dir}/js/nf/nf-client.js ${staging.dir}/js/nf/nf-common.js ${staging.dir}/js/nf/nf-dialog.js + ${staging.dir}/js/nf/nf-storage.js ${staging.dir}/js/nf/bulletin-board/nf-bulletin-board.js + + true + ${project.build.directory}/${project.build.finalName}/js/nf/login/nf-login-all.js + + ${staging.dir}/js/nf/nf-client.js + ${staging.dir}/js/nf/nf-common.js + ${staging.dir}/js/nf/nf-dialog.js + ${staging.dir}/js/nf/nf-storage.js + ${staging.dir}/js/nf/login/nf-login.js + + true ${project.build.directory}/${project.build.finalName}/css/nf-canvas-all.css @@ -418,7 +448,6 @@ ${staging.dir}/css/connection-configuration.css ${staging.dir}/css/connection-details.css ${staging.dir}/css/shell.css - ${staging.dir}/css/registration.css ${staging.dir}/css/dialog.css ${staging.dir}/css/new-processor-dialog.css ${staging.dir}/css/new-controller-service-dialog.css @@ -517,6 +546,16 @@ ${staging.dir}/css/bulletin-board.css + + true + ${project.build.directory}/${project.build.finalName}/css/nf-login-all.css + + ${staging.dir}/css/main.css + ${staging.dir}/css/banner.css + ${staging.dir}/css/dialog.css + ${staging.dir}/css/login.css + + @@ -556,6 +595,8 @@ css/nf-users-all.css.gz, css/nf-bulletin-board-all.css, css/nf-bulletin-board-all.css.gz, + css/nf-login-all.css, + css/nf-login-all.css.gz, js/*, js/d3/**/*, js/codemirror/**/*, @@ -584,6 +625,8 @@ js/nf/users/nf-users-all.js.gz, js/nf/bulletin-board/nf-bulletin-board-all.js, js/nf/bulletin-board/nf-bulletin-board-all.js.gz, + js/nf/login/nf-login-all.js, + js/nf/login/nf-login-all.js.gz, images/*, resources/*, images/*, @@ -615,7 +658,6 @@ the application classpath or the maven jetty plugin classpath defined above. --> - commons-io commons-io @@ -651,6 +693,5 @@ javax.servlet.jsp.jstl-api provided - diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE index 40d0725e15..6769105002 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/META-INF/LICENSE @@ -351,6 +351,28 @@ For details see http://jqueryui.com OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +This product bundles 'jquery.base64.js' which is available under an MIT style license. + + Copyright (c) 2013 Yannick Albert (http://yckart.com/) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + This product bundles 'SlickGrid v2.2' which is available under an MIT style license. Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/bulletin-board.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/bulletin-board.properties index 55df24470d..96a550f9a1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/bulletin-board.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/bulletin-board.properties @@ -16,6 +16,7 @@ nf.bulletin.board.script.tags=\n\ \n\ \n\ +\n\ nf.bulletin.board.style.tags=\n\ \n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties index fd2bc170d4..bf61846f44 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties @@ -19,11 +19,10 @@ nf.canvas.script.tags=\n\ \n\ \n\ -\n\ +\n\ \n\ \n\ \n\ -\n\ \n\ \n\ \n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/cluster.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/cluster.properties index f1481d852f..9327be658a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/cluster.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/cluster.properties @@ -16,6 +16,7 @@ nf.cluster.script.tags=\n\ \n\ \n\ +\n\ \n\ nf.cluster.style.tags=\n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/counters.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/counters.properties index 8b07e57228..be22f55a02 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/counters.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/counters.properties @@ -16,6 +16,7 @@ nf.counters.script.tags=\n\ \n\ \n\ +\n\ \n\ nf.counters.style.tags=\n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/history.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/history.properties index 818ee585d0..9bbe64086f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/history.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/history.properties @@ -16,6 +16,7 @@ nf.history.script.tags=\n\ \n\ \n\ +\n\ \n\ \n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login-min.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login-min.properties new file mode 100644 index 0000000000..4dafb0201f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login-min.properties @@ -0,0 +1,18 @@ +# 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. + +nf.login.script.tags= +nf.login.style.tags=\n\ + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login.properties new file mode 100644 index 0000000000..28ff4b9270 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/login.properties @@ -0,0 +1,25 @@ +# 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. + +nf.login.script.tags=\n\ +\n\ +\n\ + +nf.login.style.tags=\n\ +\n\ +\n\ +\n\ +\n\ + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/provenance.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/provenance.properties index 655d3bfb02..daa7c43ef4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/provenance.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/provenance.properties @@ -16,6 +16,7 @@ nf.provenance.script.tags=\n\ \n\ \n\ +\n\ \n\ nf.provenance.style.tags=\n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties index 90d35d93af..2417adefd7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties @@ -16,6 +16,7 @@ nf.summary.script.tags=\n\ \n\ \n\ +\n\ \n\ \n\ \n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/templates.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/templates.properties index 79c59e7a77..007bd69b54 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/templates.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/templates.properties @@ -16,6 +16,7 @@ nf.templates.script.tags=\n\ \n\ \n\ +\n\ \n\ nf.templates.style.tags=\n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/users.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/users.properties index a4e586ac97..9824a8b55d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/users.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/users.properties @@ -16,6 +16,7 @@ nf.users.script.tags=\n\ \n\ \n\ +\n\ \n\ nf.users.style.tags=\n\ diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/bulletin-board.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/bulletin-board.jsp index 6df0e51ae0..3eaa2258e4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/bulletin-board.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/bulletin-board.jsp @@ -28,6 +28,7 @@ + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp index 1bf63a6e42..f9970dfd4d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp @@ -40,8 +40,8 @@ + - @@ -73,7 +73,6 @@ Loading... - diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/cluster.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/cluster.jsp index 55ffabe3a4..acf2e55629 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/cluster.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/cluster.jsp @@ -30,6 +30,7 @@ + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/counters.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/counters.jsp index 7362cf2272..e3113f9534 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/counters.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/counters.jsp @@ -30,6 +30,7 @@ + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/history.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/history.jsp index 935512a3f7..ac28bb7662 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/history.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/history.jsp @@ -30,6 +30,7 @@ + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp new file mode 100644 index 0000000000..978d019808 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/login.jsp @@ -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. +--%> +<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %> + + + + NiFi Login + + + + ${nf.login.style.tags} + + + + + + + + + + + + ${nf.login.script.tags} + + + +
+ + + + + +
+ +
+
+ + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/message-page.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/message-page.jsp index 796877fba2..b0ba026e69 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/message-page.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/message-page.jsp @@ -27,8 +27,8 @@
-

<%= request.getAttribute("title") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getAttribute("title").toString()) %>

-

<%= request.getAttribute("messages") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getAttribute("messages").toString()) %>

+
<%= request.getAttribute("title") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getAttribute("title").toString()) %>
+
<%= request.getAttribute("messages") == null ? "" : org.apache.nifi.util.EscapeUtils.escapeHtml(request.getAttribute("messages").toString()) %>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/provenance.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/provenance.jsp index e02a7cd3c7..496bfd1775 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/provenance.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/provenance.jsp @@ -31,6 +31,7 @@ + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/summary.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/summary.jsp index e6f330582d..45dff93380 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/summary.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/summary.jsp @@ -38,6 +38,7 @@ + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/templates.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/templates.jsp index 126c3885a3..452064d6cd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/templates.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/templates.jsp @@ -30,6 +30,7 @@ + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/users.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/users.jsp index a5f422c21d..b3e09681da 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/users.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/users.jsp @@ -31,6 +31,7 @@ + diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp index f312327d5a..2ea7ca61f0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp @@ -44,6 +44,17 @@
-
DN
+
Identity
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/web.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/web.xml index d0a5e39e80..232dbcea8b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/web.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/web.xml @@ -17,18 +17,17 @@ nifi - NiFiCanvas /WEB-INF/pages/canvas.jsp NiFiCanvas + /canvas - NiFiSummary /WEB-INF/pages/summary.jsp @@ -39,7 +38,6 @@ - NiFiHistory /WEB-INF/pages/history.jsp @@ -50,7 +48,6 @@ - NiFiProvenance /WEB-INF/pages/provenance.jsp @@ -61,7 +58,6 @@ - NiFiCounters /WEB-INF/pages/counters.jsp @@ -72,7 +68,6 @@ - NiFiTemplates /WEB-INF/pages/templates.jsp @@ -83,7 +78,6 @@ - NiFiUsers /WEB-INF/pages/users.jsp @@ -94,7 +88,6 @@ - NiFiCluster /WEB-INF/pages/cluster.jsp @@ -105,7 +98,6 @@ - BulletinBoard /WEB-INF/pages/bulletin-board.jsp @@ -116,7 +108,6 @@ - MessagePage /WEB-INF/pages/message-page.jsp @@ -127,7 +118,6 @@ - DownloadSvg org.apache.nifi.web.servlet.DownloadSvg @@ -137,6 +127,16 @@ /download-svg + + + Login + /WEB-INF/pages/login.jsp + + + Login + /login + + IeEdgeHeader org.apache.nifi.web.filter.IeEdgeHeader diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css index a8866adf56..abb5ebd29c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css @@ -28,7 +28,6 @@ @import url(connection-configuration.css); @import url(connection-details.css); @import url(shell.css); -@import url(registration.css); @import url(dialog.css); @import url(new-processor-dialog.css); @import url(new-controller-service-dialog.css); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css index 0f08b47019..49dd3a0d0a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/header.css @@ -506,6 +506,24 @@ div.search-glass-pane { /* styles for the status link */ +#anonymous-user-alert { + float: left; + margin-top: -2px; + margin-right: 6px; + width: 18px; + height: 16px; + background-image: url(../images/iconAlert.png); +} + +#current-user { + float: left; + margin-right: 8px; + font-weight: bold; + max-width: 250px; + text-overflow: ellipsis; + overflow: hidden; +} + #utilities-container { float: right; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css new file mode 100644 index 0000000000..68086a7b3b --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/login.css @@ -0,0 +1,95 @@ +/* + * 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. + */ + +/* + Login Styles +*/ + +#login-contents-container { + position: absolute; + top: 0px; + left: 0px; + bottom: 0px; + right: 0px; + background: #fff url(../images/bg-error.png) left top no-repeat; + font-family: Verdana, Geneva, sans-serif; + padding-top: 100px; + padding-left: 100px; +} + +#login-message-title { + font-size: 18px; + color: #294c58; + margin-bottom: 16px; +} + +#login-message { + font-size: 11px; +} + +.login-title { + font-size: 12px; + font-weight: bold; + margin-bottom: 10px; + color: #000; +} + +/* + Login +*/ + +body.login-body input, body.login-body textarea { + width: 400px; +} + +div.login-container { + width: 412px; +} + +/* + NiFi Registration +*/ + +#nifi-user-submit-justification-container { + margin-bottom: 10px; +} + +#nifi-user-submit-justification { + font-weight: bold; +} + +#nifi-registration-justification { + height: 200px; +} + +/* + Login Progress +*/ + +#login-progress-label { + float: right; + font-weight: bold; + line-height: 16px; +} + +#login-progress-spinner { + float: right; + width: 16px; + height: 16px; + background-color: transparent; + margin-left: 3px; +} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/main.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/main.css index 95ee641d62..deadcd58c7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/main.css +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/main.css @@ -77,6 +77,17 @@ div.context-menu-provenance { background-position: top left; } +#user-logout-container { + position: absolute; + left: 478px; + top: 100px; + z-index: 1300; +} + +#user-logout { + text-decoration: underline; +} + /* General Styles */ @@ -153,7 +164,7 @@ input.filter-list { background: transparent url(../images/iconTwistArrow.png) no-repeat scroll top right; } -input[type=text], textarea { +input[type=text], input[type=password], textarea { background: white url(../images/bgInputText.png) repeat-x scroll top; border: 1px solid #ccc; font-family: Verdana; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js new file mode 100644 index 0000000000..32ceab073f --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/jquery.base64.js @@ -0,0 +1,123 @@ +/*! + * jquery.base64.js 0.0.3 - https://github.com/yckart/jquery.base64.js + * Makes Base64 en & -decoding simpler as it is. + * + * Based upon: https://gist.github.com/Yaffle/1284012 + * + * Copyright (c) 2012 Yannick Albert (http://yckart.com) + * Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php). + * 2013/02/10 + **/ +;(function($) { + + var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + a256 = '', + r64 = [256], + r256 = [256], + i = 0; + + var UTF8 = { + + /** + * Encode multi-byte Unicode string into utf-8 multiple single-byte characters + * (BMP / basic multilingual plane only) + * + * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars + * + * @param {String} strUni Unicode string to be encoded as UTF-8 + * @returns {String} encoded string + */ + encode: function(strUni) { + // use regular expressions & String.replace callback function for better efficiency + // than procedural approaches + var strUtf = strUni.replace(/[\u0080-\u07ff]/g, // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz + function(c) { + var cc = c.charCodeAt(0); + return String.fromCharCode(0xc0 | cc >> 6, 0x80 | cc & 0x3f); + }) + .replace(/[\u0800-\uffff]/g, // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz + function(c) { + var cc = c.charCodeAt(0); + return String.fromCharCode(0xe0 | cc >> 12, 0x80 | cc >> 6 & 0x3F, 0x80 | cc & 0x3f); + }); + return strUtf; + }, + + /** + * Decode utf-8 encoded string back into multi-byte Unicode characters + * + * @param {String} strUtf UTF-8 string to be decoded back to Unicode + * @returns {String} decoded string + */ + decode: function(strUtf) { + // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char! + var strUni = strUtf.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars + function(c) { // (note parentheses for precence) + var cc = ((c.charCodeAt(0) & 0x0f) << 12) | ((c.charCodeAt(1) & 0x3f) << 6) | (c.charCodeAt(2) & 0x3f); + return String.fromCharCode(cc); + }) + .replace(/[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars + function(c) { // (note parentheses for precence) + var cc = (c.charCodeAt(0) & 0x1f) << 6 | c.charCodeAt(1) & 0x3f; + return String.fromCharCode(cc); + }); + return strUni; + } + }; + + while(i < 256) { + var c = String.fromCharCode(i); + a256 += c; + r256[i] = i; + r64[i] = b64.indexOf(c); + ++i; + } + + function code(s, discard, alpha, beta, w1, w2) { + s = String(s); + var buffer = 0, + i = 0, + length = s.length, + result = '', + bitsInBuffer = 0; + + while(i < length) { + var c = s.charCodeAt(i); + c = c < 256 ? alpha[c] : -1; + + buffer = (buffer << w1) + c; + bitsInBuffer += w1; + + while(bitsInBuffer >= w2) { + bitsInBuffer -= w2; + var tmp = buffer >> bitsInBuffer; + result += beta.charAt(tmp); + buffer ^= tmp << bitsInBuffer; + } + ++i; + } + if(!discard && bitsInBuffer > 0) result += beta.charAt(buffer << (w2 - bitsInBuffer)); + return result; + } + + var Plugin = $.base64 = function(dir, input, encode) { + return input ? Plugin[dir](input, encode) : dir ? null : this; + }; + + Plugin.btoa = Plugin.encode = function(plain, utf8encode) { + plain = Plugin.raw === false || Plugin.utf8encode || utf8encode ? UTF8.encode(plain) : plain; + plain = code(plain, false, r256, b64, 8, 6); + return plain + '===='.slice((plain.length % 4) || 4); + }; + + Plugin.atob = Plugin.decode = function(coded, utf8decode) { + coded = coded.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + coded = String(coded).split('='); + var i = coded.length; + do {--i; + coded[i] = code(coded[i], true, r64, a256, 6, 8); + } while (i > 0); + coded = coded.join(''); + return Plugin.raw === false || Plugin.utf8decode || utf8decode ? UTF8.decode(coded) : coded; + }; +}(jQuery)); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/bulletin-board/nf-bulletin-board.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/bulletin-board/nf-bulletin-board.js index 656755eb0e..55d0a9d8fd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/bulletin-board/nf-bulletin-board.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/bulletin-board/nf-bulletin-board.js @@ -417,6 +417,8 @@ nf.BulletinBoard = (function () { * Initializes the bulletin board page. */ init: function () { + nf.Storage.init(); + initializePage().done(function () { start(); }); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js index 5cc1eff238..7d635349c8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js @@ -31,8 +31,10 @@ nf.CanvasHeader = (function () { return { /** * Initialize the canvas header. + * + * @argument {boolean} supportsLogin Whether login is supported */ - init: function () { + init: function (supportsLogin) { // mouse over for the reporting link nf.Common.addHoverEffect('#reporting-link', 'reporting-link', 'reporting-link-hover').click(function () { nf.Shell.showPage('summary'); @@ -139,6 +141,28 @@ nf.CanvasHeader = (function () { nf.Shell.showPage(config.urls.helpDocument); }); + // show the login link if supported and user is currently anonymous + var isAnonymous = $('#current-user').text() === nf.Common.ANONYMOUS_USER_TEXT; + if (supportsLogin === true && isAnonymous) { + // login link + $('#login-link').click(function () { + nf.Shell.showPage('login', false); + }); + } else { + $('#login-link-container').css('display', 'none'); + } + + // if login is not supported, don't show the current user + if (supportsLogin === false) { + $('#current-user-container').css('display', 'none'); + } + + // logout link + $('#logout-link').click(function () { + nf.Storage.removeItem("jwt"); + window.location = '/nifi'; + }); + // initialize the new template dialog $('#new-template-dialog').modal({ headerText: 'Create Template', @@ -154,11 +178,11 @@ nf.CanvasHeader = (function () { handler: { click: function () { var selection = nf.CanvasUtils.getSelection(); - + // color the selected components selection.each(function (d) { var selected = d3.select(this); - + var revision = nf.Client.getRevision(); var selectedData = selected.datum(); @@ -197,7 +221,7 @@ nf.CanvasHeader = (function () { }); } }); - + // close the dialog $('#fill-color-dialog').modal('hide'); } @@ -238,20 +262,20 @@ nf.CanvasHeader = (function () { }); } }); - + // updates the color if its a valid hex color string var updateColor = function () { var hex = $('#fill-color-value').val(); - + // only update the fill color when its a valid hex color string // #[six hex characters|three hex characters] case insensitive if (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex)) { $('#fill-color').minicolors('value', hex); } }; - + // apply fill color from field on blur and enter press - $('#fill-color-value').on('blur', updateColor).on('keyup', function(e) { + $('#fill-color-value').on('blur', updateColor).on('keyup', function (e) { var code = e.keyCode ? e.keyCode : e.which; if (code === $.ui.keyCode.ENTER) { updateColor(); @@ -310,23 +334,22 @@ nf.CanvasHeader = (function () { } } }); - + var toolbar = $('#toolbar'); var groupButton = $('#action-group'); - $(window).on('resize', function() { + $(window).on('resize', function () { if (toolbar.width() < MIN_TOOLBAR_WIDTH && groupButton.is(':visible')) { toolbar.find('.secondary').hide(); } else if (toolbar.width() > MIN_TOOLBAR_WIDTH && groupButton.is(':hidden')) { toolbar.find('.secondary').show(); } }); - + // set up the initial visibility if (toolbar.width() < MIN_TOOLBAR_WIDTH) { toolbar.find('.secondary').hide(); } }, - /** * Reloads and clears any warnings. */ @@ -341,7 +364,7 @@ nf.CanvasHeader = (function () { // hide the refresh link on the canvas $('#stats-last-refreshed').removeClass('alert'); $('#refresh-required-container').hide(); - + // hide the refresh link on the settings $('#settings-last-refreshed').removeClass('alert'); $('#settings-refresh-required-icon').hide(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js index 7137fe4be9..c9498fe63f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js @@ -57,6 +57,7 @@ nf.Canvas = (function () { var config = { urls: { + identity: '../nifi-api/controller/identity', authorities: '../nifi-api/controller/authorities', revision: '../nifi-api/controller/revision', status: '../nifi-api/controller/status', @@ -64,6 +65,7 @@ nf.Canvas = (function () { banners: '../nifi-api/controller/banners', controller: '../nifi-api/controller', controllerConfig: '../nifi-api/controller/config', + accessConfig: '../nifi-api/access/config', cluster: '../nifi-api/cluster', d3Script: 'js/d3/d3.min.js' } @@ -1018,122 +1020,166 @@ nf.Canvas = (function () { * Initialize NiFi. */ init: function () { - // init the registration form before performing the first query since - // the response could lead to a registration attempt - nf.Registration.init(); - - // get the controller config to register the status poller - var configXhr = $.ajax({ + // get the current user's identity + var identityXhr = $.ajax({ type: 'GET', - url: config.urls.controllerConfig, + url: config.urls.identity, dataType: 'json' }); - // create the deferred cluster request - var isClusteredRequest = $.Deferred(function (deferred) { - $.ajax({ - type: 'HEAD', - url: config.urls.cluster - }).done(function (response, status, xhr) { - clustered = true; - deferred.resolve(response, status, xhr); - }).fail(function (xhr, status, error) { - if (xhr.status === 404) { - clustered = false; - deferred.resolve('', 'success', xhr); - } else { - deferred.reject(xhr, status, error); - } - }); - }).promise(); - - // load the authorities + // get the current user's authorities var authoritiesXhr = $.ajax({ type: 'GET', url: config.urls.authorities, dataType: 'json' }); - // ensure the authorities and config request is processed first - $.when(authoritiesXhr, configXhr).done(function (authoritiesResult, configResult) { - var authoritiesResponse = authoritiesResult[0]; - var configResponse = configResult[0]; + // load the identity and authorities for the current user + var userXhr = $.Deferred(function (deferred) { + $.when(authoritiesXhr, identityXhr).done(function (authoritiesResult, identityResult) { + var authoritiesResponse = authoritiesResult[0]; + var identityResponse = identityResult[0]; - // set the user's authorities - nf.Common.setAuthorities(authoritiesResponse.authorities); + // set the user's authorities + nf.Common.setAuthorities(authoritiesResponse.authorities); - // calculate the canvas offset - var canvasContainer = $('#canvas-container'); - nf.Canvas.CANVAS_OFFSET = canvasContainer.offset().top; + // at this point the user may be themselves or anonymous - // get the config details - var configDetails = configResponse.config; + // if the user is logged, we want to determine if they were logged in using a certificate + if (identityResponse.identity !== 'anonymous') { + // rendner the users name + $('#current-user').text(identityResponse.identity).show(); - // when both request complete, load the application - isClusteredRequest.done(function () { - // get the auto refresh interval - var autoRefreshIntervalSeconds = parseInt(configDetails.autoRefreshIntervalSeconds, 10); + // render the logout button if there is a token locally + if (nf.Storage.getItem('jwt') !== null) { + $('#logout-link-container').show(); + } + } else { + // set the anonymous user label + nf.Common.setAnonymousUserLabel(); + } + deferred.resolve(); + }).fail(function (xhr, status, error) { + // there is no anonymous access and we don't know this user - open the login page which handles login/registration/etc + if (xhr.status === 401 || xhr.status === 403) { + window.location = '/nifi/login'; + } else { + deferred.reject(xhr, status, error); + } + }); + }).promise(); - // initialize whether site to site is secure - secureSiteToSite = configDetails.siteToSiteSecure; + userXhr.done(function () { + // get the controller config to register the status poller + var configXhr = $.ajax({ + type: 'GET', + url: config.urls.controllerConfig, + dataType: 'json' + }); - // load d3 - loadD3().done(function () { - nf.Storage.init(); + // get the login config + var loginXhr = $.ajax({ + type: 'GET', + url: config.urls.accessConfig, + dataType: 'json' + }); - // initialize the application - initCanvas(); - nf.Canvas.View.init(); - nf.ContextMenu.init(); - nf.CanvasToolbar.init(); - nf.CanvasToolbox.init(); - nf.CanvasHeader.init(); - nf.GraphControl.init(); - nf.Search.init(); - nf.Settings.init(); - nf.Actions.init(); + // create the deferred cluster request + var isClusteredRequest = $.Deferred(function (deferred) { + $.ajax({ + type: 'HEAD', + url: config.urls.cluster + }).done(function (response, status, xhr) { + clustered = true; + deferred.resolve(response, status, xhr); + }).fail(function (xhr, status, error) { + if (xhr.status === 404) { + clustered = false; + deferred.resolve('', 'success', xhr); + } else { + deferred.reject(xhr, status, error); + } + }); + }).promise(); - // initialize the component behaviors - nf.Draggable.init(); - nf.Selectable.init(); - nf.Connectable.init(); + // ensure the config requests are loaded + $.when(configXhr, loginXhr, userXhr).done(function (configResult, loginResult) { + var configResponse = configResult[0]; + var loginResponse = loginResult[0]; - // initialize the chart - nf.StatusHistory.init(configDetails.timeOffset); + // calculate the canvas offset + var canvasContainer = $('#canvas-container'); + nf.Canvas.CANVAS_OFFSET = canvasContainer.offset().top; - // initialize the birdseye - nf.Birdseye.init(); + // get the config details + var configDetails = configResponse.config; + var loginDetails = loginResponse.config; - // initialize components - nf.ConnectionConfiguration.init(); - nf.ControllerService.init(); - nf.ReportingTask.init(); - nf.ProcessorConfiguration.init(); - nf.ProcessGroupConfiguration.init(); - nf.RemoteProcessGroupConfiguration.init(); - nf.RemoteProcessGroupPorts.init(); - nf.PortConfiguration.init(); - nf.SecurePortConfiguration.init(); - nf.LabelConfiguration.init(); - nf.ProcessorDetails.init(); - nf.ProcessGroupDetails.init(); - nf.PortDetails.init(); - nf.SecurePortDetails.init(); - nf.ConnectionDetails.init(); - nf.RemoteProcessGroupDetails.init(); - nf.GoTo.init(); - nf.Graph.init().done(function () { - // determine the split between the polling - var pollingSplit = autoRefreshIntervalSeconds / 2; + // when both request complete, load the application + isClusteredRequest.done(function () { + // get the auto refresh interval + var autoRefreshIntervalSeconds = parseInt(configDetails.autoRefreshIntervalSeconds, 10); - // register the revision and status polling - startRevisionPolling(autoRefreshIntervalSeconds); - setTimeout(function () { - startStatusPolling(autoRefreshIntervalSeconds); - }, pollingSplit * 1000); + // initialize whether site to site is secure + secureSiteToSite = configDetails.siteToSiteSecure; - // hide the splash screen - nf.Canvas.hideSplash(); + // load d3 + loadD3().done(function () { + nf.Storage.init(); + + // initialize the application + initCanvas(); + nf.Canvas.View.init(); + nf.ContextMenu.init(); + nf.CanvasToolbar.init(); + nf.CanvasToolbox.init(); + nf.CanvasHeader.init(loginDetails.supportsLogin); + nf.GraphControl.init(); + nf.Search.init(); + nf.Settings.init(); + + // initialize the component behaviors + nf.Draggable.init(); + nf.Selectable.init(); + nf.Connectable.init(); + + // initialize the chart + nf.StatusHistory.init(configDetails.timeOffset); + + // initialize the birdseye + nf.Birdseye.init(); + + // initialize components + nf.ConnectionConfiguration.init(); + nf.ControllerService.init(); + nf.ReportingTask.init(); + nf.ProcessorConfiguration.init(); + nf.ProcessGroupConfiguration.init(); + nf.RemoteProcessGroupConfiguration.init(); + nf.RemoteProcessGroupPorts.init(); + nf.PortConfiguration.init(); + nf.SecurePortConfiguration.init(); + nf.LabelConfiguration.init(); + nf.ProcessorDetails.init(); + nf.ProcessGroupDetails.init(); + nf.PortDetails.init(); + nf.SecurePortDetails.init(); + nf.ConnectionDetails.init(); + nf.RemoteProcessGroupDetails.init(); + nf.GoTo.init(); + nf.Graph.init().done(function () { + // determine the split between the polling + var pollingSplit = autoRefreshIntervalSeconds / 2; + + // register the revision and status polling + startRevisionPolling(autoRefreshIntervalSeconds); + setTimeout(function () { + startStatusPolling(autoRefreshIntervalSeconds); + }, pollingSplit * 1000); + + // hide the splash screen + nf.Canvas.hideSplash(); + }).fail(nf.Common.handleAjaxError); }).fail(nf.Common.handleAjaxError); }).fail(nf.Common.handleAjaxError); }).fail(nf.Common.handleAjaxError); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-registration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-registration.js deleted file mode 100644 index b678e27fa7..0000000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-registration.js +++ /dev/null @@ -1,71 +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. - */ - -/* global nf */ - -nf.Registration = (function () { - - var config = { - urls: { - users: '../nifi-api/controller/users' - } - }; - - return { - /** - * Initializes the user account registration form. - */ - init: function () { - $('#registration-justification').count({ - charCountField: '#remaining-characters' - }); - - // register a click listener to expand/collapse the registration form - $('#expand-registration-button, #expand-registration-text').click(function () { - var registrationForm = $('#registration-form'); - if (registrationForm.is(':visible')) { - $('#expand-registration-button').removeClass('registration-expanded').addClass('collapsed'); - } else { - $('#expand-registration-button').removeClass('registration-collapsed').addClass('expanded'); - } - registrationForm.toggle(); - }); - - // register a click listener for submitting user account requests - $('#registration-form-submit').one('click', function () { - var justification = $('#registration-justification').val(); - - // attempt to create the user account registration - $.ajax({ - type: 'POST', - url: config.urls.users, - data: { - 'justification': justification - } - }).done(function (response) { - // hide the registration pane - $('#registration-pane').hide(); - - // show the message pane - $('#message-pane').show(); - $('#message-title').text('Thanks'); - $('#message-content').text('Your request will be processed shortly.'); - }).fail(nf.Common.handleAjaxError); - }); - } - }; -}()); \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-storage.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-storage.js deleted file mode 100644 index 3937970d17..0000000000 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-storage.js +++ /dev/null @@ -1,139 +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. - */ - -/* global nf, d3 */ - -nf.Storage = (function () { - - // Store items for two days before being eligible for removal. - var TWO_DAYS = 86400000 * 2; - - // alternative to local storage thats used when localStorage isn't available - var alternativeStorage; - - // determine if this browser supports local storage - var SUPPORTS_LOCAL_STORAGE = (function () { - var test = 'test'; - try { - localStorage.setItem(test, test); - localStorage.removeItem(test); - return true; - } catch (e) { - return false; - } - }()); - - return { - /** - * Initializes the storage. When the browser supports localStorage, - * the items will be persisted for two days. Once the scripts runs - * thereafter, all eligible items will be removed. If localStorage - * is not supported, a data structure will be used to hold items. - * This strategy does not support persistence. - */ - init: function () { - // if we support local storage, see if anything can be removed - if (SUPPORTS_LOCAL_STORAGE) { - for (var i = 0; i < localStorage.length; i++) { - try { - // get the next item - var key = localStorage.key(i); - var entry = JSON.parse(localStorage.getItem(key)); - - // get the expiration - var expires = new Date(entry.expires); - var now = new Date(); - - // if the expiration date has passed, remove it - if (expires.valueOf() < now.valueOf()) { - localStorage.removeItem(key); - } - } catch (e) { - // likely unable to parse the item - } - } - } else { - alternativeStorage = d3.map(); - } - }, - - /** - * Stores the specified item. If supported, will be persisted - * in localStorage. - * - * @param {type} key - * @param {type} item - */ - setItem: function (key, item) { - if (SUPPORTS_LOCAL_STORAGE) { - // calculate the expiration - var expires = new Date().valueOf() + TWO_DAYS; - - // create the entry - var entry = { - expires: expires, - item: item - }; - - // store the item - localStorage.setItem(key, JSON.stringify(entry)); - } else { - alternativeStorage.set(key, item); - } - }, - - /** - * Gets the item with the specified key. If an item with this key does - * not exist, null is returned. If an item exists but cannot be parsed - * or is malformed/unrecognized, null is returned. - * - * @param {type} key - */ - getItem: function (key) { - if (SUPPORTS_LOCAL_STORAGE) { - try { - // parse the entry - var entry = JSON.parse(localStorage.getItem(key)); - - // ensure the entry and item are present - if (nf.Common.isDefinedAndNotNull(entry) && nf.Common.isDefinedAndNotNull(entry.item)) { - return entry.item; - } else { - return null; - } - } catch (e) { - return null; - } - } else { - return alternativeStorage.get(key); - } - }, - - /** - * Removes the item with the specified key. - * - * @param {type} key - */ - removeItem: function (key) { - if (SUPPORTS_LOCAL_STORAGE) { - localStorage.removeItem(key); - } else { - alternativeStorage.remove(key); - } - } - }; -}()); \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster.js index 5f00e2ca82..1eafa67c95 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster.js @@ -123,6 +123,8 @@ nf.Cluster = (function () { * Initializes the counters page. */ init: function () { + nf.Storage.init(); + // load the users authorities loadAuthorities().done(function () { // create the counters table diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/counters/nf-counters.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/counters/nf-counters.js index cbfe3360ef..6af3d20eb3 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/counters/nf-counters.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/counters/nf-counters.js @@ -123,6 +123,8 @@ nf.Counters = (function () { * Initializes the counters page. */ init: function () { + nf.Storage.init(); + // load the users authorities loadAuthorities().done(function () { // create the counters table diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/history/nf-history.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/history/nf-history.js index 516507cb16..8f749c0102 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/history/nf-history.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/history/nf-history.js @@ -124,6 +124,8 @@ nf.History = (function () { * Initializes the status page. */ init: function () { + nf.Storage.init(); + // load the users authorities loadAuthorities().done(function () { // create the history table diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js new file mode 100644 index 0000000000..6cefd4faba --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/login/nf-login.js @@ -0,0 +1,302 @@ +/* + * 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. + */ + +/* global nf, top */ + +$(document).ready(function () { + nf.Login.init(); +}); + +nf.Login = (function () { + + var supportsAnonymous = false; + + var config = { + urls: { + identity: '../nifi-api/controller/identity', + users: '../nifi-api/controller/users', + token: '../nifi-api/access/token', + accessStatus: '../nifi-api/access', + accessConfig: '../nifi-api/access/config' + } + }; + + var initializeMessage = function () { + $('#login-message-container').show(); + }; + + var showLogin = function () { + // reset the forms + $('#username').val(''); + $('#password').val(''); + $('#login-submission-button').text('Log in'); + + // update the form visibility + $('#login-container').show(); + $('#nifi-registration-container').hide(); + + // set the focus + $('#username').focus(); + }; + + var initializeNiFiRegistration = function () { + $('#nifi-registration-justification').count({ + charCountField: '#remaining-characters' + }); + + // toggle between signup and login + $('#login-to-account-link').on('click', function () { + showLogin(); + }); + }; + + var showNiFiRegistration = function () { + // reset the forms + $('#login-submission-button').text('Submit'); + $('#nifi-registration-justification').val(''); + + // update the form visibility + $('#login-container').hide(); + $('#nifi-registration-container').show(); + }; + + var initializeSubmission = function () { + $('#login-submission-button').on('click', function () { + if ($('#login-container').is(':visible')) { + login(); + } else if ($('#nifi-registration-container').is(':visible')) { + submitJustification(); + } + }); + + $('#login-submission-container').show(); + }; + + var login = function () { + // show the logging message... + $('#login-progress-label').text('Logging in...'); + $('#login-progress-container').show(); + $('#login-submission-container').hide(); + + // login submit + $.ajax({ + type: 'POST', + url: config.urls.token, + data: { + 'username': $('#username').val(), + 'password': $('#password').val() + } + }).done(function (jwt) { + // get the payload and store the token with the appropirate expiration + var token = nf.Common.getJwtPayload(jwt); + var expiration = parseInt(token['exp'], 10) * nf.Common.MILLIS_PER_SECOND; + nf.Storage.setItem('jwt', jwt, expiration); + + // check to see if they actually have access now + $.ajax({ + type: 'GET', + url: config.urls.identity, + dataType: 'json' + }).done(function (response) { + if (response.identity === 'anonymous') { + showLogoutLink(); + + // schedule automatic token refresh + nf.Common.scheduleTokenRefresh(); + + // show the user + $('#nifi-user-submit-justification').text(token['preferred_username']); + + // show the registration form + initializeNiFiRegistration(); + showNiFiRegistration(); + + // update the form visibility + $('#login-submission-container').show(); + $('#login-progress-container').hide(); + } else { + // reload as appropriate - no need to schedule token refresh as the page is reloading + if (top !== window) { + parent.window.location = '/nifi'; + } else { + window.location = '/nifi'; + } + } + }).fail(function (xhr, status, error) { + showLogoutLink(); + + // schedule automatic token refresh + nf.Common.scheduleTokenRefresh(); + + // show the user + $('#nifi-user-submit-justification').text(token['preferred_username']); + + if (xhr.status === 401) { + initializeNiFiRegistration(); + showNiFiRegistration(); + + // update the form visibility + $('#login-submission-container').show(); + $('#login-progress-container').hide(); + } else { + $('#login-message-title').text('Unable to log in'); + $('#login-message').text(xhr.responseText); + + // update visibility + $('#login-container').hide(); + $('#login-submission-container').hide(); + $('#login-progress-container').hide(); + $('#login-message-container').show(); + } + }); + }).fail(function (xhr, status, error) { + nf.Dialog.showOkDialog({ + dialogContent: nf.Common.escapeHtml(xhr.responseText), + overlayBackground: false + }); + + // update the form visibility + $('#login-submission-container').show(); + $('#login-progress-container').hide(); + }); + }; + + var submitJustification = function () { + // show the logging message... + $('#login-progress-label').text('Submitting...'); + $('#login-progress-container').show(); + $('#login-submission-container').hide(); + + // attempt to create the nifi account registration + $.ajax({ + type: 'POST', + url: config.urls.users, + data: { + 'justification': $('#nifi-registration-justification').val() + } + }).done(function (response) { + var markup = 'An administrator will process your request shortly.'; + if (supportsAnonymous === true) { + markup += '

In the meantime you can continue accessing anonymously.'; + } + + $('#login-message-title').text('Thanks!'); + $('#login-message').html(markup); + }).fail(function (xhr, status, error) { + $('#login-message-title').text('Unable to submit justification'); + $('#login-message').text(xhr.responseText); + }).always(function () { + // update form visibility + $('#nifi-registration-container').hide(); + $('#login-submission-container').hide(); + $('#login-progress-container').hide(); + $('#login-message-container').show(); + }); + }; + + var showLogoutLink = function () { + nf.Common.showLogoutLink(); + }; + + return { + /** + * Initializes the login page. + */ + init: function () { + nf.Storage.init(); + + if (nf.Storage.getItem('jwt') !== null) { + showLogoutLink(); + } + + // access status + var accessStatus = $.ajax({ + type: 'GET', + url: config.urls.accessStatus, + dataType: 'json' + }).fail(function (xhr, status, error) { + $('#login-message-title').text('Unable to check Access Status'); + $('#login-message').text(xhr.responseText); + initializeMessage(); + }); + + // access config + var accessConfigXhr = $.ajax({ + type: 'GET', + url: config.urls.accessConfig, + dataType: 'json' + }); + + $.when(accessStatus, accessConfigXhr).done(function (accessStatusResult, accessConfigResult) { + var accessStatusResponse = accessStatusResult[0]; + var accessStatus = accessStatusResponse.accessStatus; + + var accessConfigResponse = accessConfigResult[0]; + var accessConfig = accessConfigResponse.config; + + // record whether this NiFi supports anonymous access + supportsAnonymous = accessConfig.supportsAnonymous; + + // possible login states + var needsLogin = false; + var needsNiFiRegistration = false; + var showMessage = false; + + // handle the status appropriately + if (accessStatus.status === 'UNKNOWN') { + needsLogin = true; + } else if (accessStatus.status === 'UNREGISTERED') { + needsNiFiRegistration = true; + + $('#nifi-user-submit-justification').text(accessStatus.username); + } else if (accessStatus.status === 'NOT_ACTIVE') { + showMessage = true; + + $('#login-message-title').text('Access Denied'); + $('#login-message').text(accessStatus.message); + } else if (accessStatus.status === 'ACTIVE') { + showMessage = true; + + $('#login-message-title').text('Success'); + $('#login-message').text('Your account is active and you are already logged in.'); + } + + // if login is required, verify its supported + if (accessConfig.supportsLogin === false && needsLogin === true) { + $('#login-message-title').text('Access Denied'); + $('#login-message').text('This NiFi is not configured to support login.'); + showMessage = true; + needsLogin = false; + } + + // initialize the page as appropriate + if (showMessage === true) { + initializeMessage(); + } else if (needsLogin === true) { + showLogin(); + } else if (needsNiFiRegistration === true) { + initializeNiFiRegistration(); + showNiFiRegistration(); + } + + if (needsLogin === true || needsNiFiRegistration === true) { + initializeSubmission(); + } + }); + } + }; +}()); \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js index 06c34f9e65..321044fe86 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js @@ -50,948 +50,1110 @@ $(document).ready(function () { // hide the loading indicator $('div.loading-container').removeClass('ajax-loading'); }); + + // include jwt when possible + $.ajaxSetup({ + 'beforeSend': function(xhr) { + var hadToken = nf.Storage.hasItem('jwt'); + + // get the token to include in all requests + var token = nf.Storage.getItem('jwt'); + if (token !== null) { + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + } else { + // if the current user was logged in with a token and the token just expired, reload + if (hadToken === true) { + return false; + } + } + } + }); // initialize the tooltips $('img.setting-icon').qtip(nf.Common.config.tooltipConfig); + + // shows the logout link in the message-pane when appropriate and schedule token refresh + if (nf.Storage.getItem('jwt') !== null) { + $('#user-logout-container').show(); + nf.Common.scheduleTokenRefresh(); + } + + // handle logout + $('#user-logout').on('click', function () { + nf.Storage.removeItem('jwt'); + window.location = '/nifi/login'; + }); }); // Define a common utility class used across the entire application. -nf.Common = { - config: { - sensitiveText: 'Sensitive value set', - tooltipConfig: { - style: { - classes: 'nifi-tooltip' - }, - show: { - solo: true, - effect: false - }, - hide: { - effect: false - }, - position: { - at: 'top right', - my: 'bottom left' - } - } - }, +nf.Common = (function () { + // interval for cancelling token refresh when necessary + var tokenRefreshInterval = null; - /** - * Determines if the current broswer supports SVG. - */ - SUPPORTS_SVG: !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect, - - /** - * The authorities for the current user. - */ - authorities: undefined, - - /** - * Sets the authorities for the current user. - * - * @argument {array} roles The current users authorities - */ - setAuthorities: function (roles) { - nf.Common.authorities = roles; - }, - - /** - * Loads a script at the specified URL. Supports caching the script on the browser. - * - * @param {string} url - */ - cachedScript: function (url) { - return $.ajax({ - dataType: 'script', - cache: true, - url: url - }); - }, - - /** - * Determines whether the current user can access provenance. - * - * @returns {boolean} - */ - canAccessProvenance: function () { - var canAccessProvenance = false; - if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { - $.each(nf.Common.authorities, function (i, authority) { - if (authority === 'ROLE_PROVENANCE') { - canAccessProvenance = true; - return false; - } - }); - } - return canAccessProvenance; - }, - - /** - * Returns whether or not the current user is a DFM. - */ - isDFM: function () { - var dfm = false; - if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { - $.each(nf.Common.authorities, function (i, authority) { - if (authority === 'ROLE_DFM') { - dfm = true; - return false; - } - }); - } - return dfm; - }, - - /** - * Returns whether or not the current user is a DFM. - */ - isAdmin: function () { - var admin = false; - if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { - $.each(nf.Common.authorities, function (i, authority) { - if (authority === 'ROLE_ADMIN') { - admin = true; - return false; - } - }); - } - return admin; - }, - - /** - * Adds a mouse over effect for the specified selector using - * the specified styles. - * - * @argument {string} selector The selector for the element to add a hover effect for - * @argument {string} normalStyle The css style for the normal state - * @argument {string} overStyle The css style for the over state - */ - addHoverEffect: function (selector, normalStyle, overStyle) { - $(document).on('mouseenter', selector, function () { - $(this).removeClass(normalStyle).addClass(overStyle); - }).on('mouseleave', selector, function () { - $(this).removeClass(overStyle).addClass(normalStyle); - }); - return $(selector).addClass(normalStyle); - }, - - /** - * Method for handling ajax errors. - * - * @argument {object} xhr The XmlHttpRequest - * @argument {string} status The status of the request - * @argument {string} error The error - */ - handleAjaxError: function (xhr, status, error) { - // show the account registration page if necessary - if (xhr.status === 401 && $('#registration-pane').length) { - // show the registration pane - $('#registration-pane').show(); - - // close the canvas - nf.Common.closeCanvas(); - return; - } + return { + ANONYMOUS_USER_TEXT: 'Anonymous user', - // if an error occurs while the splash screen is visible close the canvas show the error message - if ($('#splash').is(':visible')) { - $('#message-title').text('An unexpected error has occurred'); - if ($.trim(xhr.responseText) === '') { - $('#message-content').text('Please check the logs.'); - } else { - $('#message-content').text(xhr.responseText); + config: { + sensitiveText: 'Sensitive value set', + tooltipConfig: { + style: { + classes: 'nifi-tooltip' + }, + show: { + solo: true, + effect: false + }, + hide: { + effect: false + }, + position: { + at: 'top right', + my: 'bottom left' + } } + }, - // show the error pane - $('#message-pane').show(); + /** + * Determines if the current broswer supports SVG. + */ + SUPPORTS_SVG: !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect, - // close the canvas - nf.Common.closeCanvas(); - return; - } - - // status code 400, 404, and 409 are expected response codes for common errors. - if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) { - nf.Dialog.showOkDialog({ - dialogContent: nf.Common.escapeHtml(xhr.responseText), - overlayBackground: false + /** + * The authorities for the current user. + */ + authorities: undefined, + + /** + * Sets the authorities for the current user. + * + * @argument {array} roles The current users authorities + */ + setAuthorities: function (roles) { + nf.Common.authorities = roles; + }, + + /** + * Loads a script at the specified URL. Supports caching the script on the browser. + * + * @param {string} url + */ + cachedScript: function (url) { + return $.ajax({ + dataType: 'script', + cache: true, + url: url }); - } else { - if (xhr.status < 99 || xhr.status === 12007 || xhr.status === 12029) { - var content = 'Please ensure the application is running and check the logs for any errors.'; - if (nf.Common.isDefinedAndNotNull(status)) { - if (status === 'timeout') { - content = 'Request has timed out. Please ensure the application is running and check the logs for any errors.'; - } else if (status === 'abort') { - content = 'Request has been aborted.'; - } else if (status === 'No Transport') { - content = 'Request transport mechanism failed. Please ensure the host where the application is running is accessible.'; + }, + + /** + * Automatically refresh tokens by checking once an hour if its going to expire soon. + */ + scheduleTokenRefresh: function () { + // if we are currently polling for token refresh, cancel it + if (tokenRefreshInterval !== null) { + clearInterval(tokenRefreshInterval); + } + + // set the interval to one hour + var interval = nf.Common.MILLIS_PER_MINUTE; + + var checkExpiration = function () { + var expiration = nf.Storage.getItemExpiration('jwt'); + + // ensure there is an expiration and token present + if (expiration !== null) { + var expirationDate = new Date(expiration); + var now = new Date(); + + // get the time remainging plus a little bonus time to reload the token + var timeRemaining = expirationDate.valueOf() - now.valueOf() - (30 * nf.Common.MILLIS_PER_SECOND); + if (timeRemaining < interval) { + if ($('#current-user').text() !== nf.Common.ANONYMOUS_USER_TEXT && !$('#anonymous-user-alert').is(':visible')) { + // if the token will expire before the next interval minus some bonus time, notify the user to re-login + $('#anonymous-user-alert').show().qtip($.extend({}, nf.Common.config.tooltipConfig, { + content: 'Your session will expire soon. Please log in again to avoid being automatically logged out.', + position: { + my: 'top right', + at: 'bottom left' + } + })); + } } } - $('#message-title').text('Unable to communicate with NiFi'); - $('#message-content').text(content); - } else if (xhr.status === 401) { - $('#message-title').text('Unauthorized'); - if ($.trim(xhr.responseText) === '') { - $('#message-content').text('Authorization is required to use this NiFi.'); - } else { - $('#message-content').text(xhr.responseText); + }; + + // perform initial check + checkExpiration(); + + // schedule subsequent checks + tokenRefreshInterval = setInterval(checkExpiration, interval); + }, + + /** + * Sets the anonymous user label. + */ + setAnonymousUserLabel: function () { + var anonymousUserAlert = $('#anonymous-user-alert'); + if (anonymousUserAlert.data('qtip')) { + anonymousUserAlert.qtip('api').destroy(true); + } + + // alert user's of anonymous access + anonymousUserAlert.show().qtip($.extend({}, nf.Common.config.tooltipConfig, { + content: 'You are accessing with limited authority. Log in or request an account to access with additional authority granted to you by an administrator.', + position: { + my: 'top right', + at: 'bottom left' } - } else if (xhr.status === 403) { - $('#message-title').text('Forbidden'); - if ($.trim(xhr.responseText) === '') { - $('#message-content').text('Unable to authorize you to use this NiFi.'); - } else { - $('#message-content').text(xhr.responseText); + })); + + // render the anonymous user text + $('#current-user').text(nf.Common.ANONYMOUS_USER_TEXT).show(); + }, + + /** + * Extracts the subject from the specified jwt. If the jwt is not as expected + * an empty string is returned. + * + * @param {string} jwt + * @returns {string} + */ + getJwtPayload: function (jwt) { + if (nf.Common.isDefinedAndNotNull(jwt)) { + var segments = jwt.split(/\./); + if (segments.length !== 3) { + return ''; } - } else if (xhr.status === 500) { - $('#message-title').text('An unexpected error has occurred'); - if ($.trim(xhr.responseText) === '') { - $('#message-content').text('An error occurred communicating with the application core. Please check the logs and fix any configuration issues before restarting.'); + + var rawPayload = $.base64.atob(segments[1]); + var payload = JSON.parse(rawPayload); + + if (nf.Common.isDefinedAndNotNull(payload)) { + return payload; } else { - $('#message-content').text(xhr.responseText); + return null; } - } else if (xhr.status === 200 || xhr.status === 201) { - $('#message-title').text('Parse Error'); - if ($.trim(xhr.responseText) === '') { - $('#message-content').text('Unable to interpret response from NiFi.'); - } else { - $('#message-content').text(xhr.responseText); - } - } else { - $('#message-title').text(xhr.status + ': Unexpected Response'); - $('#message-content').text('An unexpected error has occurred. Please check the logs.'); } - // show the error pane - $('#message-pane').show(); + return null; + }, - // close the canvas - nf.Common.closeCanvas(); - } - }, - - /** - * Closes the canvas by removing the splash screen and stats poller. - */ - closeCanvas: function () { - // ensure this javascript has been loaded in the nf canvas page - if (nf.Common.isDefinedAndNotNull(nf.Canvas)) { - // hide the splash screen if required + /** + * Determines whether the current user can access provenance. + * + * @returns {boolean} + */ + canAccessProvenance: function () { + var canAccessProvenance = false; + if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { + $.each(nf.Common.authorities, function (i, authority) { + if (authority === 'ROLE_PROVENANCE') { + canAccessProvenance = true; + return false; + } + }); + } + return canAccessProvenance; + }, + + /** + * Returns whether or not the current user is a DFM. + */ + isDFM: function () { + var dfm = false; + if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { + $.each(nf.Common.authorities, function (i, authority) { + if (authority === 'ROLE_DFM') { + dfm = true; + return false; + } + }); + } + return dfm; + }, + + /** + * Returns whether or not the current user is a DFM. + */ + isAdmin: function () { + var admin = false; + if (nf.Common.isDefinedAndNotNull(nf.Common.authorities)) { + $.each(nf.Common.authorities, function (i, authority) { + if (authority === 'ROLE_ADMIN') { + admin = true; + return false; + } + }); + } + return admin; + }, + + /** + * Adds a mouse over effect for the specified selector using + * the specified styles. + * + * @argument {string} selector The selector for the element to add a hover effect for + * @argument {string} normalStyle The css style for the normal state + * @argument {string} overStyle The css style for the over state + */ + addHoverEffect: function (selector, normalStyle, overStyle) { + $(document).on('mouseenter', selector, function () { + $(this).removeClass(normalStyle).addClass(overStyle); + }).on('mouseleave', selector, function () { + $(this).removeClass(overStyle).addClass(normalStyle); + }); + return $(selector).addClass(normalStyle); + }, + + /** + * Method for handling ajax errors. + * + * @argument {object} xhr The XmlHttpRequest + * @argument {string} status The status of the request + * @argument {string} error The error + */ + handleAjaxError: function (xhr, status, error) { + if (status === 'canceled') { + if ($('#splash').is(':visible')) { + $('#message-title').text('Session Expired'); + $('#message-content').text('Your session has expired. Please reload to log in again.'); + + // show the error pane + $('#message-pane').show(); + } else { + nf.Dialog.showOkDialog({ + dialogContent: 'Your session has expired. Please press Ok to log in again.', + overlayBackground: false, + okHandler: function () { + window.location = '/nifi'; + } + }); + } + + // close the canvas + nf.Common.closeCanvas(); + return; + } + + // if an error occurs while the splash screen is visible close the canvas show the error message if ($('#splash').is(':visible')) { - nf.Canvas.hideSplash(); - } - - // hide the context menu - nf.ContextMenu.hide(); - - // shut off the auto refresh - nf.Canvas.stopRevisionPolling(); - nf.Canvas.stopStatusPolling(); - } - }, - - /** - * Populates the specified field with the specified value. If the value is - * undefined, the field will read 'No value set.' If the value is an empty - * string, the field will read 'Empty string set.' - * - * @argument {string} target The dom Id of the target - * @argument {string} value The value - */ - populateField: function (target, value) { - if (nf.Common.isUndefined(value) || nf.Common.isNull(value)) { - return $('#' + target).addClass('unset').text('No value set'); - } else if (value === '') { - return $('#' + target).addClass('blank').text('Empty string set'); - } else { - return $('#' + target).text(value); - } - }, - - /** - * Clears the specified field. Removes any style that may have been applied - * by a preceeding call to populateField. - * - * @argument {string} target The dom Id of the target - */ - clearField: function (target) { - return $('#' + target).removeClass('unset blank').text(''); - }, - - /** - * Cleans up any tooltips that have been created for the specified container. - * - * @param {jQuery} container - * @param {string} tooltipTarget - */ - cleanUpTooltips: function(container, tooltipTarget) { - container.find(tooltipTarget).each(function () { - var tip = $(this); - if (tip.data('qtip')) { - var api = tip.qtip('api'); - api.destroy(true); - } - }); - }, - - /** - * Removes all read only property detail dialogs. - */ - removeAllPropertyDetailDialogs: function () { - var propertyDetails = $('body').children('div.property-detail'); - propertyDetails.find('div.nfel-editor').nfeditor('destroy'); - propertyDetails.hide().remove(); - }, - - /** - * Formats the tooltip for the specified property. - * - * @param {object} propertyDescriptor The property descriptor - * @param {object} propertyHistory The property history - * @returns {string} - */ - formatPropertyTooltip: function (propertyDescriptor, propertyHistory) { - var tipContent = []; - - // show the property description if applicable - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - if (!nf.Common.isBlank(propertyDescriptor.description)) { - tipContent.push(nf.Common.escapeHtml(propertyDescriptor.description)); - } - if (!nf.Common.isBlank(propertyDescriptor.defaultValue)) { - tipContent.push('Default value: ' + nf.Common.escapeHtml(propertyDescriptor.defaultValue)); - } - if (!nf.Common.isBlank(propertyDescriptor.supportsEl)) { - tipContent.push('Supports expression language: ' + nf.Common.escapeHtml(propertyDescriptor.supportsEl)); - } - } - - if (nf.Common.isDefinedAndNotNull(propertyHistory)) { - if (!nf.Common.isEmpty(propertyHistory.previousValues)) { - var history = []; - $.each(propertyHistory.previousValues, function (_, previousValue) { - history.push('
  • ' + nf.Common.escapeHtml(previousValue.previousValue) + ' - ' + nf.Common.escapeHtml(previousValue.timestamp) + ' (' + nf.Common.escapeHtml(previousValue.userName) + ')
  • '); - }); - tipContent.push('History:
      ' + history.join('') + '
    '); - } - } - - if (tipContent.length > 0) { - return tipContent.join('

    '); - } else { - return null; - } - }, - - /** - * Formats the specified property (name and value) accordingly. - * - * @argument {string} name The name of the property - * @argument {string} value The value of the property - */ - formatProperty: function (name, value) { - return '
    ' + nf.Common.formatValue(name) + ': ' + nf.Common.formatValue(value) + '
    '; - }, - - /** - * Formats the specified value accordingly. - * - * @argument {string} value The value of the property - */ - formatValue: function (value) { - if (nf.Common.isDefinedAndNotNull(value)) { - if (value === '') { - return 'Empty string set'; - } else { - return nf.Common.escapeHtml(value); - } - } else { - return 'No value set'; - } - }, - - /** - * HTML escapes the specified string. If the string is null - * or undefined, an empty string is returned. - * - * @returns {string} - */ - escapeHtml: (function () { - var entityMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/' - }; - - return function (string) { - if (nf.Common.isDefinedAndNotNull(string)) { - return String(string).replace(/[&<>"'\/]/g, function (s) { - return entityMap[s]; - }); - } else { - return ''; - } - }; - }()), - - /** - * Determines if the specified property is sensitive. - * - * @argument {object} propertyDescriptor The property descriptor - */ - isSensitiveProperty: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.sensitive === true; - } else { - return false; - } - }, - - /** - * Determines if the specified property is required. - * - * @param {object} propertyDescriptor The property descriptor - */ - isRequiredProperty: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.required === true; - } else { - return false; - } - }, - - /** - * Determines if the specified property is required. - * - * @param {object} propertyDescriptor The property descriptor - */ - isDynamicProperty: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.dynamic === true; - } else { - return false; - } - }, - - /** - * Gets the allowable values for the specified property. - * - * @argument {object} propertyDescriptor The property descriptor - */ - getAllowableValues: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.allowableValues; - } else { - return null; - } - }, - - /** - * Returns whether the specified property supports EL. - * - * @param {object} propertyDescriptor The property descriptor - */ - supportsEl: function (propertyDescriptor) { - if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { - return propertyDescriptor.supportsEl === true; - } else { - return false; - } - }, - - /** - * Creates a form inline in order to submit the specified params to the specified URL - * using the specified method. - * - * @param {string} url The URL - * @param {object} params An object with the params to include in the submission - */ - post: function (url, params) { - // temporarily override beforeunload - var previousBeforeUnload = window.onbeforeunload; - window.onbeforeunload = null; - - // create a form for submission - var form = $('
    ').attr({ - 'method': 'POST', - 'action': url, - 'style': 'display: none;' - }); - - // add each parameter when specified - if (nf.Common.isDefinedAndNotNull(params)) { - $.each(params, function (name, value) { - $('').attr('name', name).val(value).appendTo(form); - }); - } - - // submit the form and clean up - form.appendTo('body').submit().remove(); - - // restore previous beforeunload if necessary - if (previousBeforeUnload !== null) { - window.onbeforeunload = previousBeforeUnload; - } - }, - - /** - * Formats the specified array as an unordered list. If the array is not an - * array, null is returned. - * - * @argument {array} array The array to convert into an unordered list - */ - formatUnorderedList: function (array) { - if ($.isArray(array)) { - var ul = $('
      '); - $.each(array, function (_, item) { - var li = $('
    • ').appendTo(ul); - if (item instanceof jQuery) { - li.append(item); + if (xhr.status === 401) { + $('#message-title').text('Unauthorized'); + } else if (xhr.status === 403) { + $('#message-title').text('Access Denied'); } else { - li.text(item); + $('#message-title').text('An unexpected error has occurred'); + } + + if ($.trim(xhr.responseText) === '') { + $('#message-content').text('Please check the logs.'); + } else { + $('#message-content').text(xhr.responseText); + } + + // show the error pane + $('#message-pane').show(); + + // close the canvas + nf.Common.closeCanvas(); + return; + } + + // status code 400, 404, and 409 are expected response codes for common errors. + if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) { + nf.Dialog.showOkDialog({ + dialogContent: nf.Common.escapeHtml(xhr.responseText), + overlayBackground: false + }); + } else { + if (xhr.status < 99 || xhr.status === 12007 || xhr.status === 12029) { + var content = 'Please ensure the application is running and check the logs for any errors.'; + if (nf.Common.isDefinedAndNotNull(status)) { + if (status === 'timeout') { + content = 'Request has timed out. Please ensure the application is running and check the logs for any errors.'; + } else if (status === 'abort') { + content = 'Request has been aborted.'; + } else if (status === 'No Transport') { + content = 'Request transport mechanism failed. Please ensure the host where the application is running is accessible.'; + } + } + $('#message-title').text('Unable to communicate with NiFi'); + $('#message-content').text(content); + } else if (xhr.status === 401) { + $('#message-title').text('Unauthorized'); + if ($.trim(xhr.responseText) === '') { + $('#message-content').text('Authorization is required to use this NiFi.'); + } else { + $('#message-content').text(xhr.responseText); + } + } else if (xhr.status === 403) { + $('#message-title').text('Access Denied'); + if ($.trim(xhr.responseText) === '') { + $('#message-content').text('Unable to authorize you to use this NiFi.'); + } else { + $('#message-content').text(xhr.responseText); + } + } else if (xhr.status === 500) { + $('#message-title').text('An unexpected error has occurred'); + if ($.trim(xhr.responseText) === '') { + $('#message-content').text('An error occurred communicating with the application core. Please check the logs and fix any configuration issues before restarting.'); + } else { + $('#message-content').text(xhr.responseText); + } + } else if (xhr.status === 200 || xhr.status === 201) { + $('#message-title').text('Parse Error'); + if ($.trim(xhr.responseText) === '') { + $('#message-content').text('Unable to interpret response from NiFi.'); + } else { + $('#message-content').text(xhr.responseText); + } + } else { + $('#message-title').text(xhr.status + ': Unexpected Response'); + $('#message-content').text('An unexpected error has occurred. Please check the logs.'); + } + + // show the error pane + $('#message-pane').show(); + + // close the canvas + nf.Common.closeCanvas(); + } + }, + + /** + * Closes the canvas by removing the splash screen and stats poller. + */ + closeCanvas: function () { + nf.Common.showLogoutLink(); + + // ensure this javascript has been loaded in the nf canvas page + if (nf.Common.isDefinedAndNotNull(nf.Canvas)) { + // hide the splash screen if required + if ($('#splash').is(':visible')) { + nf.Canvas.hideSplash(); + } + + // hide the context menu + nf.ContextMenu.hide(); + + // shut off the auto refresh + nf.Canvas.stopRevisionPolling(); + nf.Canvas.stopStatusPolling(); + } + }, + + /** + * Shows the logout link if appropriate. + */ + showLogoutLink: function () { + if (nf.Storage.getItem('jwt') === null) { + $('#user-logout-container').hide(); + } else { + $('#user-logout-container').show(); + } + }, + + /** + * Populates the specified field with the specified value. If the value is + * undefined, the field will read 'No value set.' If the value is an empty + * string, the field will read 'Empty string set.' + * + * @argument {string} target The dom Id of the target + * @argument {string} value The value + */ + populateField: function (target, value) { + if (nf.Common.isUndefined(value) || nf.Common.isNull(value)) { + return $('#' + target).addClass('unset').text('No value set'); + } else if (value === '') { + return $('#' + target).addClass('blank').text('Empty string set'); + } else { + return $('#' + target).text(value); + } + }, + + /** + * Clears the specified field. Removes any style that may have been applied + * by a preceeding call to populateField. + * + * @argument {string} target The dom Id of the target + */ + clearField: function (target) { + return $('#' + target).removeClass('unset blank').text(''); + }, + + /** + * Cleans up any tooltips that have been created for the specified container. + * + * @param {jQuery} container + * @param {string} tooltipTarget + */ + cleanUpTooltips: function(container, tooltipTarget) { + container.find(tooltipTarget).each(function () { + var tip = $(this); + if (tip.data('qtip')) { + var api = tip.qtip('api'); + api.destroy(true); } }); - return ul; - } else { - return null; - } - }, - - /** - * Extracts the contents of the specified str after the strToFind. If the - * strToFind is not found or the last part of the str, an empty string is - * returned. - * - * @argument {string} str The full string - * @argument {string} strToFind The substring to find - */ - substringAfterLast: function (str, strToFind) { - var result = ''; - var indexOfStrToFind = str.lastIndexOf(strToFind); - if (indexOfStrToFind >= 0) { - var indexAfterStrToFind = indexOfStrToFind + strToFind.length; - if (indexAfterStrToFind < str.length) { - result = str.substr(indexAfterStrToFind); + }, + + /** + * Removes all read only property detail dialogs. + */ + removeAllPropertyDetailDialogs: function () { + var propertyDetails = $('body').children('div.property-detail'); + propertyDetails.find('div.nfel-editor').nfeditor('destroy'); + propertyDetails.hide().remove(); + }, + + /** + * Formats the tooltip for the specified property. + * + * @param {object} propertyDescriptor The property descriptor + * @param {object} propertyHistory The property history + * @returns {string} + */ + formatPropertyTooltip: function (propertyDescriptor, propertyHistory) { + var tipContent = []; + + // show the property description if applicable + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + if (!nf.Common.isBlank(propertyDescriptor.description)) { + tipContent.push(nf.Common.escapeHtml(propertyDescriptor.description)); + } + if (!nf.Common.isBlank(propertyDescriptor.defaultValue)) { + tipContent.push('Default value: ' + nf.Common.escapeHtml(propertyDescriptor.defaultValue)); + } + if (!nf.Common.isBlank(propertyDescriptor.supportsEl)) { + tipContent.push('Supports expression language: ' + nf.Common.escapeHtml(propertyDescriptor.supportsEl)); + } } - } - return result; - }, - - /** - * Updates the mouse pointer. - * - * @argument {string} domId The id of the element for the new cursor style - * @argument {boolean} isMouseOver Whether or not the mouse is over the element - */ - setCursor: function (domId, isMouseOver) { - if (isMouseOver) { - $('#' + domId).addClass('pointer'); - } else { - $('#' + domId).removeClass('pointer'); - } - }, - - /** - * Constants for time duration formatting. - */ - MILLIS_PER_DAY: 86400000, - MILLIS_PER_HOUR: 3600000, - MILLIS_PER_MINUTE: 60000, - MILLIS_PER_SECOND: 1000, - - /** - * Formats the specified duration. - * - * @param {integer} duration in millis - */ - formatDuration: function (duration) { - // don't support sub millisecond resolution - duration = duration < 1 ? 0 : duration; - // determine the number of days in the specified duration - var days = duration / nf.Common.MILLIS_PER_DAY; - days = days >= 1 ? parseInt(days, 10) : 0; - duration %= nf.Common.MILLIS_PER_DAY; + if (nf.Common.isDefinedAndNotNull(propertyHistory)) { + if (!nf.Common.isEmpty(propertyHistory.previousValues)) { + var history = []; + $.each(propertyHistory.previousValues, function (_, previousValue) { + history.push('
    • ' + nf.Common.escapeHtml(previousValue.previousValue) + ' - ' + nf.Common.escapeHtml(previousValue.timestamp) + ' (' + nf.Common.escapeHtml(previousValue.userName) + ')
    • '); + }); + tipContent.push('History:
        ' + history.join('') + '
      '); + } + } - // remaining duration should be less than 1 day, get number of hours - var hours = duration / nf.Common.MILLIS_PER_HOUR; - hours = hours >= 1 ? parseInt(hours, 10) : 0; - duration %= nf.Common.MILLIS_PER_HOUR; + if (tipContent.length > 0) { + return tipContent.join('

      '); + } else { + return null; + } + }, - // remaining duration should be less than 1 hour, get number of minutes - var minutes = duration / nf.Common.MILLIS_PER_MINUTE; - minutes = minutes >= 1 ? parseInt(minutes, 10) : 0; - duration %= nf.Common.MILLIS_PER_MINUTE; + /** + * Formats the specified property (name and value) accordingly. + * + * @argument {string} name The name of the property + * @argument {string} value The value of the property + */ + formatProperty: function (name, value) { + return '
      ' + nf.Common.formatValue(name) + ': ' + nf.Common.formatValue(value) + '
      '; + }, - // remaining duration should be less than 1 minute, get number of seconds - var seconds = duration / nf.Common.MILLIS_PER_SECOND; - seconds = seconds >= 1 ? parseInt(seconds, 10) : 0; - - // remaining duration is the number millis (don't support sub millisecond resolution) - duration = Math.floor(duration % nf.Common.MILLIS_PER_SECOND); - - // format the time - var time = nf.Common.pad(hours, 2, '0') + - ':' + - nf.Common.pad(minutes, 2, '0') + - ':' + - nf.Common.pad(seconds, 2, '0') + - '.' + - nf.Common.pad(duration, 3, '0'); - - // only include days if appropriate - if (days > 0) { - return days + ' days and ' + time; - } else { - return time; - } - }, - - /** - * Constants for formatting data size. - */ - BYTES_IN_KILOBYTE: 1024, - BYTES_IN_MEGABYTE: 1048576, - BYTES_IN_GIGABYTE: 1073741824, - BYTES_IN_TERABYTE: 1099511627776, - - /** - * Formats the specified number of bytes into a human readable string. - * - * @param {integer} dataSize - * @returns {string} - */ - formatDataSize: function (dataSize) { - // check terabytes - var dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_TERABYTE); - if (dataSizeToFormat > 1) { - return dataSizeToFormat.toFixed(2) + " TB"; - } - - // check gigabytes - dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_GIGABYTE); - if (dataSizeToFormat > 1) { - return dataSizeToFormat.toFixed(2) + " GB"; - } - - // check megabytes - dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_MEGABYTE); - if (dataSizeToFormat > 1) { - return dataSizeToFormat.toFixed(2) + " MB"; - } - - // check kilobytes - dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_KILOBYTE); - if (dataSizeToFormat > 1) { - return dataSizeToFormat.toFixed(2) + " KB"; - } - - // default to bytes - return parseFloat(dataSize).toFixed(2) + " bytes"; - }, - - /** - * Formats the specified integer as a string (adding commas). At this - * point this does not take into account any locales. - * - * @param {integer} integer - */ - formatInteger: function (integer) { - var string = integer + ''; - var regex = /(\d+)(\d{3})/; - while (regex.test(string)) { - string = string.replace(regex, '$1' + ',' + '$2'); - } - return string; - }, - - /** - * Formats the specified float using two demical places. - * - * @param {float} f - */ - formatFloat: function (f) { - if (nf.Common.isUndefinedOrNull(f)) { - return 0.00 + ''; - } - return f.toFixed(2) + ''; - }, - - /** - * Pads the specified value to the specified width with the specified character. - * If the specified value is already wider than the specified width, the original - * value is returned. - * - * @param {integer} value - * @param {integer} width - * @param {string} character - * @returns {string} - */ - pad: function (value, width, character) { - var s = value + ''; - - // pad until wide enough - while (s.length < width) { - s = character + s; - } - - return s; - }, - - /** - * Formats the specified DateTime. - * - * @param {Date} date - * @returns {String} - */ - formatDateTime: function (date) { - return nf.Common.pad(date.getMonth() + 1, 2, '0') + - '/' + - nf.Common.pad(date.getDate(), 2, '0') + - '/' + - nf.Common.pad(date.getFullYear(), 2, '0') + - ' ' + - nf.Common.pad(date.getHours(), 2, '0') + - ':' + - nf.Common.pad(date.getMinutes(), 2, '0') + - ':' + - nf.Common.pad(date.getSeconds(), 2, '0') + - '.' + - nf.Common.pad(date.getMilliseconds(), 3, '0'); - }, - - /** - * Parses the specified date time into a Date object. The resulting - * object does not account for timezone and should only be used for - * performing relative comparisons. - * - * @param {string} rawDateTime - * @returns {Date} - */ - parseDateTime: function (rawDateTime) { - // handle non date values - if (!nf.Common.isDefinedAndNotNull(rawDateTime)) { - return new Date(); - } - if (rawDateTime === 'No value set') { - return new Date(); - } - if (rawDateTime === 'Empty string set') { - return new Date(); - } - - // parse the date time - var dateTime = rawDateTime.split(/ /); - - // ensure the correct number of tokens - if (dateTime.length !== 3) { - return new Date(); - } - - // get the date and time - var date = dateTime[0].split(/\//); - var time = dateTime[1].split(/:/); - - // ensure the correct number of tokens - if (date.length !== 3 || time.length !== 3) { - return new Date(); - } - - // detect if there is millis - var seconds = time[2].split(/\./); - if (seconds.length === 2) { - return new Date(parseInt(date[2], 10), parseInt(date[0], 10), parseInt(date[1], 10), parseInt(time[0], 10), parseInt(time[1], 10), parseInt(seconds[0], 10), parseInt(seconds[1], 10)); - } else { - return new Date(parseInt(date[2], 10), parseInt(date[0], 10), parseInt(date[1], 10), parseInt(time[0], 10), parseInt(time[1], 10), parseInt(time[2], 10), 0); - } - }, - - /** - * Parses the specified duration and returns the total number of millis. - * - * @param {string} rawDuration - * @returns {number} The number of millis - */ - parseDuration: function (rawDuration) { - var duration = rawDuration.split(/:/); - - // ensure the appropriate number of tokens - if (duration.length !== 3) { - return 0; - } - - // detect if there is millis - var seconds = duration[2].split(/\./); - if (seconds.length === 2) { - return new Date(1970, 0, 1, parseInt(duration[0], 10), parseInt(duration[1], 10), parseInt(seconds[0], 10), parseInt(seconds[1], 10)).getTime(); - } else { - return new Date(1970, 0, 1, parseInt(duration[0], 10), parseInt(duration[1], 10), parseInt(duration[2], 10), 0).getTime(); - } - }, - - /** - * Parses the specified size. - * - * @param {string} rawSize - * @returns {int} - */ - parseSize: function (rawSize) { - var tokens = rawSize.split(/ /); - var size = parseFloat(tokens[0].replace(/,/g, '')); - var units = tokens[1]; - - if (units === 'KB') { - return size * 1024; - } else if (units === 'MB') { - return size * 1024 * 1024; - } else if (units === 'GB') { - return size * 1024 * 1024 * 1024; - } else if (units === 'TB') { - return size * 1024 * 1024 * 1024 * 1024; - } else { - return size; - } - }, - - /** - * Parses the specified count. - * - * @param {string} rawCount - * @returns {int} - */ - parseCount: function (rawCount) { - // extract the count - var count = rawCount.split(/ /, 1); - - // ensure the string was split successfully - if (count.length !== 1) { - return 0; - } - - // convert the count to an integer - var intCount = parseInt(count[0].replace(/,/g, ''), 10); - - // ensure it was parsable as an integer - if (isNaN(intCount)) { - return 0; - } - return intCount; - }, - - /** - * Determines if the specified object is defined and not null. - * - * @argument {object} obj The object to test - */ - isDefinedAndNotNull: function (obj) { - return !nf.Common.isUndefined(obj) && !nf.Common.isNull(obj); - }, - - /** - * Determines if the specified object is undefined or null. - * - * @param {object} obj The object to test - */ - isUndefinedOrNull: function (obj) { - return nf.Common.isUndefined(obj) || nf.Common.isNull(obj); - }, - - /** - * Determines if the specified object is undefined. - * - * @argument {object} obj The object to test - */ - isUndefined: function (obj) { - return typeof obj === 'undefined'; - }, - - /** - * Determines whether the specified string is blank (or null or undefined). - * - * @argument {string} str The string to test - */ - isBlank: function (str) { - return nf.Common.isUndefined(str) || nf.Common.isNull(str) || $.trim(str) === ''; - }, - - /** - * Determines if the specified object is null. - * - * @argument {object} obj The object to test - */ - isNull: function (obj) { - return obj === null; - }, - - /** - * Determines if the specified array is empty. If the specified arg is not an - * array, then true is returned. - * - * @argument {array} arr The array to test - */ - isEmpty: function (arr) { - return $.isArray(arr) ? arr.length === 0 : true; - }, - - /** - * Determines if these are the same bulletins. If both arguments are not - * arrays, false is returned. - * - * @param {array} bulletins - * @param {array} otherBulletins - * @returns {boolean} - */ - doBulletinsDiffer: function (bulletins, otherBulletins) { - if ($.isArray(bulletins) && $.isArray(otherBulletins)) { - if (bulletins.length === otherBulletins.length) { - for (var i = 0; i < bulletins.length; i++) { - if (bulletins[i].id !== otherBulletins[i].id) { - return true; - } + /** + * Formats the specified value accordingly. + * + * @argument {string} value The value of the property + */ + formatValue: function (value) { + if (nf.Common.isDefinedAndNotNull(value)) { + if (value === '') { + return 'Empty string set'; + } else { + return nf.Common.escapeHtml(value); } } else { + return 'No value set'; + } + }, + + /** + * HTML escapes the specified string. If the string is null + * or undefined, an empty string is returned. + * + * @returns {string} + */ + escapeHtml: (function () { + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + }; + + return function (string) { + if (nf.Common.isDefinedAndNotNull(string)) { + return String(string).replace(/[&<>"'\/]/g, function (s) { + return entityMap[s]; + }); + } else { + return ''; + } + }; + }()), + + /** + * Determines if the specified property is sensitive. + * + * @argument {object} propertyDescriptor The property descriptor + */ + isSensitiveProperty: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.sensitive === true; + } else { + return false; + } + }, + + /** + * Determines if the specified property is required. + * + * @param {object} propertyDescriptor The property descriptor + */ + isRequiredProperty: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.required === true; + } else { + return false; + } + }, + + /** + * Determines if the specified property is required. + * + * @param {object} propertyDescriptor The property descriptor + */ + isDynamicProperty: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.dynamic === true; + } else { + return false; + } + }, + + /** + * Gets the allowable values for the specified property. + * + * @argument {object} propertyDescriptor The property descriptor + */ + getAllowableValues: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.allowableValues; + } else { + return null; + } + }, + + /** + * Returns whether the specified property supports EL. + * + * @param {object} propertyDescriptor The property descriptor + */ + supportsEl: function (propertyDescriptor) { + if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) { + return propertyDescriptor.supportsEl === true; + } else { + return false; + } + }, + + /** + * Creates a form inline in order to submit the specified params to the specified URL + * using the specified method. + * + * @param {string} url The URL + * @param {object} params An object with the params to include in the submission + */ + post: function (url, params) { + // temporarily override beforeunload + var previousBeforeUnload = window.onbeforeunload; + window.onbeforeunload = null; + + // create a form for submission + var form = $('
      ').attr({ + 'method': 'POST', + 'action': url, + 'style': 'display: none;' + }); + + // add each parameter when specified + if (nf.Common.isDefinedAndNotNull(params)) { + $.each(params, function (name, value) { + $('').attr('name', name).val(value).appendTo(form); + }); + } + + // submit the form and clean up + form.appendTo('body').submit().remove(); + + // restore previous beforeunload if necessary + if (previousBeforeUnload !== null) { + window.onbeforeunload = previousBeforeUnload; + } + }, + + /** + * Formats the specified array as an unordered list. If the array is not an + * array, null is returned. + * + * @argument {array} array The array to convert into an unordered list + */ + formatUnorderedList: function (array) { + if ($.isArray(array)) { + var ul = $('
        '); + $.each(array, function (_, item) { + var li = $('
      • ').appendTo(ul); + if (item instanceof jQuery) { + li.append(item); + } else { + li.text(item); + } + }); + return ul; + } else { + return null; + } + }, + + /** + * Extracts the contents of the specified str after the strToFind. If the + * strToFind is not found or the last part of the str, an empty string is + * returned. + * + * @argument {string} str The full string + * @argument {string} strToFind The substring to find + */ + substringAfterLast: function (str, strToFind) { + var result = ''; + var indexOfStrToFind = str.lastIndexOf(strToFind); + if (indexOfStrToFind >= 0) { + var indexAfterStrToFind = indexOfStrToFind + strToFind.length; + if (indexAfterStrToFind < str.length) { + result = str.substr(indexAfterStrToFind); + } + } + return result; + }, + + /** + * Updates the mouse pointer. + * + * @argument {string} domId The id of the element for the new cursor style + * @argument {boolean} isMouseOver Whether or not the mouse is over the element + */ + setCursor: function (domId, isMouseOver) { + if (isMouseOver) { + $('#' + domId).addClass('pointer'); + } else { + $('#' + domId).removeClass('pointer'); + } + }, + + /** + * Constants for time duration formatting. + */ + MILLIS_PER_DAY: 86400000, + MILLIS_PER_HOUR: 3600000, + MILLIS_PER_MINUTE: 60000, + MILLIS_PER_SECOND: 1000, + + /** + * Formats the specified duration. + * + * @param {integer} duration in millis + */ + formatDuration: function (duration) { + // don't support sub millisecond resolution + duration = duration < 1 ? 0 : duration; + + // determine the number of days in the specified duration + var days = duration / nf.Common.MILLIS_PER_DAY; + days = days >= 1 ? parseInt(days, 10) : 0; + duration %= nf.Common.MILLIS_PER_DAY; + + // remaining duration should be less than 1 day, get number of hours + var hours = duration / nf.Common.MILLIS_PER_HOUR; + hours = hours >= 1 ? parseInt(hours, 10) : 0; + duration %= nf.Common.MILLIS_PER_HOUR; + + // remaining duration should be less than 1 hour, get number of minutes + var minutes = duration / nf.Common.MILLIS_PER_MINUTE; + minutes = minutes >= 1 ? parseInt(minutes, 10) : 0; + duration %= nf.Common.MILLIS_PER_MINUTE; + + // remaining duration should be less than 1 minute, get number of seconds + var seconds = duration / nf.Common.MILLIS_PER_SECOND; + seconds = seconds >= 1 ? parseInt(seconds, 10) : 0; + + // remaining duration is the number millis (don't support sub millisecond resolution) + duration = Math.floor(duration % nf.Common.MILLIS_PER_SECOND); + + // format the time + var time = nf.Common.pad(hours, 2, '0') + + ':' + + nf.Common.pad(minutes, 2, '0') + + ':' + + nf.Common.pad(seconds, 2, '0') + + '.' + + nf.Common.pad(duration, 3, '0'); + + // only include days if appropriate + if (days > 0) { + return days + ' days and ' + time; + } else { + return time; + } + }, + + /** + * Constants for formatting data size. + */ + BYTES_IN_KILOBYTE: 1024, + BYTES_IN_MEGABYTE: 1048576, + BYTES_IN_GIGABYTE: 1073741824, + BYTES_IN_TERABYTE: 1099511627776, + + /** + * Formats the specified number of bytes into a human readable string. + * + * @param {integer} dataSize + * @returns {string} + */ + formatDataSize: function (dataSize) { + // check terabytes + var dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_TERABYTE); + if (dataSizeToFormat > 1) { + return dataSizeToFormat.toFixed(2) + " TB"; + } + + // check gigabytes + dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_GIGABYTE); + if (dataSizeToFormat > 1) { + return dataSizeToFormat.toFixed(2) + " GB"; + } + + // check megabytes + dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_MEGABYTE); + if (dataSizeToFormat > 1) { + return dataSizeToFormat.toFixed(2) + " MB"; + } + + // check kilobytes + dataSizeToFormat = parseFloat(dataSize / nf.Common.BYTES_IN_KILOBYTE); + if (dataSizeToFormat > 1) { + return dataSizeToFormat.toFixed(2) + " KB"; + } + + // default to bytes + return parseFloat(dataSize).toFixed(2) + " bytes"; + }, + + /** + * Formats the specified integer as a string (adding commas). At this + * point this does not take into account any locales. + * + * @param {integer} integer + */ + formatInteger: function (integer) { + var string = integer + ''; + var regex = /(\d+)(\d{3})/; + while (regex.test(string)) { + string = string.replace(regex, '$1' + ',' + '$2'); + } + return string; + }, + + /** + * Formats the specified float using two demical places. + * + * @param {float} f + */ + formatFloat: function (f) { + if (nf.Common.isUndefinedOrNull(f)) { + return 0.00 + ''; + } + return f.toFixed(2) + ''; + }, + + /** + * Pads the specified value to the specified width with the specified character. + * If the specified value is already wider than the specified width, the original + * value is returned. + * + * @param {integer} value + * @param {integer} width + * @param {string} character + * @returns {string} + */ + pad: function (value, width, character) { + var s = value + ''; + + // pad until wide enough + while (s.length < width) { + s = character + s; + } + + return s; + }, + + /** + * Formats the specified DateTime. + * + * @param {Date} date + * @returns {String} + */ + formatDateTime: function (date) { + return nf.Common.pad(date.getMonth() + 1, 2, '0') + + '/' + + nf.Common.pad(date.getDate(), 2, '0') + + '/' + + nf.Common.pad(date.getFullYear(), 2, '0') + + ' ' + + nf.Common.pad(date.getHours(), 2, '0') + + ':' + + nf.Common.pad(date.getMinutes(), 2, '0') + + ':' + + nf.Common.pad(date.getSeconds(), 2, '0') + + '.' + + nf.Common.pad(date.getMilliseconds(), 3, '0'); + }, + + /** + * Parses the specified date time into a Date object. The resulting + * object does not account for timezone and should only be used for + * performing relative comparisons. + * + * @param {string} rawDateTime + * @returns {Date} + */ + parseDateTime: function (rawDateTime) { + // handle non date values + if (!nf.Common.isDefinedAndNotNull(rawDateTime)) { + return new Date(); + } + if (rawDateTime === 'No value set') { + return new Date(); + } + if (rawDateTime === 'Empty string set') { + return new Date(); + } + + // parse the date time + var dateTime = rawDateTime.split(/ /); + + // ensure the correct number of tokens + if (dateTime.length !== 3) { + return new Date(); + } + + // get the date and time + var date = dateTime[0].split(/\//); + var time = dateTime[1].split(/:/); + + // ensure the correct number of tokens + if (date.length !== 3 || time.length !== 3) { + return new Date(); + } + + // detect if there is millis + var seconds = time[2].split(/\./); + if (seconds.length === 2) { + return new Date(parseInt(date[2], 10), parseInt(date[0], 10), parseInt(date[1], 10), parseInt(time[0], 10), parseInt(time[1], 10), parseInt(seconds[0], 10), parseInt(seconds[1], 10)); + } else { + return new Date(parseInt(date[2], 10), parseInt(date[0], 10), parseInt(date[1], 10), parseInt(time[0], 10), parseInt(time[1], 10), parseInt(time[2], 10), 0); + } + }, + + /** + * Parses the specified duration and returns the total number of millis. + * + * @param {string} rawDuration + * @returns {number} The number of millis + */ + parseDuration: function (rawDuration) { + var duration = rawDuration.split(/:/); + + // ensure the appropriate number of tokens + if (duration.length !== 3) { + return 0; + } + + // detect if there is millis + var seconds = duration[2].split(/\./); + if (seconds.length === 2) { + return new Date(1970, 0, 1, parseInt(duration[0], 10), parseInt(duration[1], 10), parseInt(seconds[0], 10), parseInt(seconds[1], 10)).getTime(); + } else { + return new Date(1970, 0, 1, parseInt(duration[0], 10), parseInt(duration[1], 10), parseInt(duration[2], 10), 0).getTime(); + } + }, + + /** + * Parses the specified size. + * + * @param {string} rawSize + * @returns {int} + */ + parseSize: function (rawSize) { + var tokens = rawSize.split(/ /); + var size = parseFloat(tokens[0].replace(/,/g, '')); + var units = tokens[1]; + + if (units === 'KB') { + return size * 1024; + } else if (units === 'MB') { + return size * 1024 * 1024; + } else if (units === 'GB') { + return size * 1024 * 1024 * 1024; + } else if (units === 'TB') { + return size * 1024 * 1024 * 1024 * 1024; + } else { + return size; + } + }, + + /** + * Parses the specified count. + * + * @param {string} rawCount + * @returns {int} + */ + parseCount: function (rawCount) { + // extract the count + var count = rawCount.split(/ /, 1); + + // ensure the string was split successfully + if (count.length !== 1) { + return 0; + } + + // convert the count to an integer + var intCount = parseInt(count[0].replace(/,/g, ''), 10); + + // ensure it was parsable as an integer + if (isNaN(intCount)) { + return 0; + } + return intCount; + }, + + /** + * Determines if the specified object is defined and not null. + * + * @argument {object} obj The object to test + */ + isDefinedAndNotNull: function (obj) { + return !nf.Common.isUndefined(obj) && !nf.Common.isNull(obj); + }, + + /** + * Determines if the specified object is undefined or null. + * + * @param {object} obj The object to test + */ + isUndefinedOrNull: function (obj) { + return nf.Common.isUndefined(obj) || nf.Common.isNull(obj); + }, + + /** + * Determines if the specified object is undefined. + * + * @argument {object} obj The object to test + */ + isUndefined: function (obj) { + return typeof obj === 'undefined'; + }, + + /** + * Determines whether the specified string is blank (or null or undefined). + * + * @argument {string} str The string to test + */ + isBlank: function (str) { + return nf.Common.isUndefined(str) || nf.Common.isNull(str) || $.trim(str) === ''; + }, + + /** + * Determines if the specified object is null. + * + * @argument {object} obj The object to test + */ + isNull: function (obj) { + return obj === null; + }, + + /** + * Determines if the specified array is empty. If the specified arg is not an + * array, then true is returned. + * + * @argument {array} arr The array to test + */ + isEmpty: function (arr) { + return $.isArray(arr) ? arr.length === 0 : true; + }, + + /** + * Determines if these are the same bulletins. If both arguments are not + * arrays, false is returned. + * + * @param {array} bulletins + * @param {array} otherBulletins + * @returns {boolean} + */ + doBulletinsDiffer: function (bulletins, otherBulletins) { + if ($.isArray(bulletins) && $.isArray(otherBulletins)) { + if (bulletins.length === otherBulletins.length) { + for (var i = 0; i < bulletins.length; i++) { + if (bulletins[i].id !== otherBulletins[i].id) { + return true; + } + } + } else { + return true; + } + } else if ($.isArray(bulletins) || $.isArray(otherBulletins)) { return true; } - } else if ($.isArray(bulletins) || $.isArray(otherBulletins)) { - return true; + return false; + }, + + /** + * Formats the specified bulletin list. + * + * @argument {array} bulletins The bulletins + * @return {array} The jQuery objects + */ + getFormattedBulletins: function (bulletins) { + var formattedBulletins = []; + $.each(bulletins, function (j, bulletin) { + // format the node address + var nodeAddress = ''; + if (nf.Common.isDefinedAndNotNull(bulletin.nodeAddress)) { + nodeAddress = '- ' + nf.Common.escapeHtml(bulletin.nodeAddress) + ' - '; + } + + // set the bulletin message (treat as text) + var bulletinMessage = $('
        ').css({
        +                    'white-space': 'pre-wrap'
        +                }).text(bulletin.message);
        +
        +                // create the bulletin message
        +                var formattedBulletin = $('
        ' + + nf.Common.escapeHtml(bulletin.timestamp) + ' ' + + nodeAddress + ' ' + + '' + nf.Common.escapeHtml(bulletin.level) + ' ' + + '
        ').append(bulletinMessage); + + formattedBulletins.push(formattedBulletin); + }); + return formattedBulletins; } - return false; - }, - - /** - * Formats the specified bulletin list. - * - * @argument {array} bulletins The bulletins - * @return {array} The jQuery objects - */ - getFormattedBulletins: function (bulletins) { - var formattedBulletins = []; - $.each(bulletins, function (j, bulletin) { - // format the node address - var nodeAddress = ''; - if (nf.Common.isDefinedAndNotNull(bulletin.nodeAddress)) { - nodeAddress = '- ' + nf.Common.escapeHtml(bulletin.nodeAddress) + ' - '; - } - - // set the bulletin message (treat as text) - var bulletinMessage = $('
        ').css({
        -                'white-space': 'pre-wrap'
        -            }).text(bulletin.message);
        -
        -            // create the bulletin message
        -            var formattedBulletin = $('
        ' + - nf.Common.escapeHtml(bulletin.timestamp) + ' ' + - nodeAddress + ' ' + - '' + nf.Common.escapeHtml(bulletin.level) + ' ' + - '
        ').append(bulletinMessage); - - formattedBulletins.push(formattedBulletin); - }); - return formattedBulletins; - } -}; + }; +}()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js index 13f5cf3004..9a926ef97f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-dialog.js @@ -23,15 +23,6 @@ $(document).ready(function () { // configure the ok dialog $('#nf-ok-dialog').modal({ - buttons: [{ - buttonText: 'Ok', - handler: { - click: function () { - // close the dialog - $('#nf-ok-dialog').modal('hide'); - } - } - }], handler: { close: function () { // clear the content @@ -78,6 +69,20 @@ nf.Dialog = (function () { var content = $('

        ').append(options.dialogContent); $('#nf-ok-dialog-content').append(content); + // update the button model + $('#nf-ok-dialog').modal('setButtonModel', [{ + buttonText: 'Ok', + handler: { + click: function () { + // close the dialog + $('#nf-ok-dialog').modal('hide'); + if (typeof options.okHandler === 'function') { + options.okHandler.call(this); + } + } + } + }]); + // show the dialog $('#nf-ok-dialog').modal('setHeaderText', options.headerText).modal('setOverlayBackground', options.overlayBackground).modal('show'); }, diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js new file mode 100644 index 0000000000..7b0c3f61d6 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-storage.js @@ -0,0 +1,172 @@ +/* + * 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. + */ + +/* global nf, d3 */ + +nf.Storage = (function () { + + // Store items for two days before being eligible for removal. + var TWO_DAYS = nf.Common.MILLIS_PER_DAY * 2; + + /** + * Checks the expiration for the specified entry. + * + * @param {object} entry + * @returns {boolean} + */ + var checkExpiration = function (entry) { + if (nf.Common.isDefinedAndNotNull(entry.expires)) { + // get the expiration + var expires = new Date(entry.expires); + var now = new Date(); + + // return whether the expiration date has passed + return expires.valueOf() < now.valueOf(); + } else { + return false; + } + }; + + /** + * Gets an enty for the key. The entry expiration is not checked. + * + * @param {string} key + */ + var getEntry = function (key) { + try { + // parse the entry + var entry = JSON.parse(localStorage.getItem(key)); + + // ensure the entry and item are present + if (nf.Common.isDefinedAndNotNull(entry)) { + return entry; + } else { + return null; + } + } catch (e) { + return null; + } + }; + + return { + /** + * Initializes the storage. Items will be persisted for two days. Once the scripts runs + * thereafter, all eligible items will be removed. This strategy does not support persistence. + */ + init: function () { + for (var i = 0; i < localStorage.length; i++) { + try { + // get the next item + var key = localStorage.key(i); + + // attempt to get the item which will expire if necessary + nf.Storage.getItem(key); + } catch (e) { + } + } + }, + + /** + * Stores the specified item. + * + * @param {string} key + * @param {object} item + * @param {integer} expires + */ + setItem: function (key, item, expires) { + // calculate the expiration + expires = nf.Common.isDefinedAndNotNull(expires) ? expires : new Date().valueOf() + TWO_DAYS; + + // create the entry + var entry = { + expires: expires, + item: item + }; + + // store the item + localStorage.setItem(key, JSON.stringify(entry)); + }, + + /** + * Returns whether there is an entry for this key. This will not check the expiration. If + * the entry is expired, it will return null on a subsequent getItem invocation. + * + * @param {string} key + * @returns {boolean} + */ + hasItem: function (key) { + return getEntry(key) !== null; + }, + + /** + * Gets the item with the specified key. If an item with this key does + * not exist, null is returned. If an item exists but cannot be parsed + * or is malformed/unrecognized, null is returned. + * + * @param {type} key + */ + getItem: function (key) { + var entry = getEntry(key); + if (entry === null) { + return null; + } + + // if the entry is expired, drop it and return null + if (checkExpiration(entry)) { + nf.Storage.removeItem(key); + return null; + } + + // if the entry has the specified field return its value + if (nf.Common.isDefinedAndNotNull(entry['item'])) { + return entry['item']; + } else { + return null; + } + }, + + /** + * Gets the expiration for the specified item. This will not check the expiration. If + * the entry is expired, it will return null on a subsequent getItem invocation. + * + * @param {string} key + * @returns {integer} + */ + getItemExpiration: function (key) { + var entry = getEntry(key); + if (entry === null) { + return null; + } + + // if the entry has the specified field return its value + if (nf.Common.isDefinedAndNotNull(entry['expires'])) { + return entry['expires']; + } else { + return null; + } + }, + + /** + * Removes the item with the specified key. + * + * @param {type} key + */ + removeItem: function (key) { + localStorage.removeItem(key); + } + }; +}()); \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance.js index 0edbe082b5..876e06ddaa 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance.js @@ -174,6 +174,8 @@ nf.Provenance = (function () { * Initializes the status page. */ init: function () { + nf.Storage.init(); + // load the users authorities and detect if the NiFi is clustered $.when(loadControllerConfig(), loadAuthorities(), detectedCluster()).done(function () { // create the provenance table diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/summary/nf-summary.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/summary/nf-summary.js index 97f8626c7e..4f066629e7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/summary/nf-summary.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/summary/nf-summary.js @@ -128,6 +128,8 @@ nf.Summary = (function () { * Initializes the status page. */ init: function () { + nf.Storage.init(); + // intialize the summary table initializeSummaryTable().done(function () { // load the table diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates.js index b55bee2ded..678e352c88 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates.js @@ -204,6 +204,8 @@ nf.Templates = (function () { * Initializes the templates page. */ init: function () { + nf.Storage.init(); + // load the users authorities loadAuthorities().done(function () { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js index 96f73a5ec9..9364aecb3b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js @@ -116,6 +116,8 @@ nf.Users = (function () { * Initializes the counters page. */ init: function () { + nf.Storage.init(); + // load the users authorities loadAuthorities().done(function () { // create the counters table diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers-nar/pom.xml b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers-nar/pom.xml new file mode 100644 index 0000000000..59681b93c8 --- /dev/null +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers-nar/pom.xml @@ -0,0 +1,32 @@ + + + + 4.0.0 + + org.apache.nifi + nifi-ldap-iaa-providers-bundle + 0.3.1-SNAPSHOT + + nifi-ldap-iaa-providers-nar + nar + + + org.apache.nifi + nifi-ldap-iaa-providers + + + nifi-ldap-iaa-providers-nar + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/pom.xml b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/pom.xml new file mode 100644 index 0000000000..5cbd6f9cab --- /dev/null +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/pom.xml @@ -0,0 +1,60 @@ + + + + 4.0.0 + + org.apache.nifi + nifi-ldap-iaa-providers-bundle + 0.3.1-SNAPSHOT + + nifi-ldap-iaa-providers + jar + + + org.apache.nifi + nifi-api + + + org.apache.nifi + nifi-utils + + + org.apache.nifi + nifi-security-utils + + + org.springframework.security + spring-security-ldap + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.springframework + spring-tx + + + org.apache.commons + commons-lang3 + + + nifi-ldap-iaa-providers + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapAuthenticationStrategy.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapAuthenticationStrategy.java new file mode 100644 index 0000000000..7124ce162c --- /dev/null +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapAuthenticationStrategy.java @@ -0,0 +1,27 @@ +/* + * 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.ldap; + +/** + * + */ +public enum LdapAuthenticationStrategy { + + ANONYMOUS, + SIMPLE, + START_TLS +} diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java new file mode 100644 index 0000000000..4dc7ea4383 --- /dev/null +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java @@ -0,0 +1,279 @@ +/* + * 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.ldap; + +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authentication.AuthenticationResponse; +import org.apache.nifi.authentication.LoginCredentials; +import org.apache.nifi.authentication.LoginIdentityProvider; +import org.apache.nifi.authentication.LoginIdentityProviderConfigurationContext; +import org.apache.nifi.authentication.LoginIdentityProviderInitializationContext; +import org.apache.nifi.authentication.exception.IdentityAccessException; +import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException; +import org.apache.nifi.authorization.exception.ProviderCreationException; +import org.apache.nifi.authorization.exception.ProviderDestructionException; +import org.apache.nifi.security.util.SslContextFactory; +import org.apache.nifi.security.util.SslContextFactory.ClientAuth; +import org.apache.nifi.util.FormatUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ldap.CommunicationException; +import org.springframework.ldap.core.support.AbstractTlsDirContextAuthenticationStrategy; +import org.springframework.ldap.core.support.DefaultTlsDirContextAuthenticationStrategy; +import org.springframework.ldap.core.support.LdapContextSource; +import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider; +import org.springframework.security.ldap.authentication.BindAuthenticator; +import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; +import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; +import org.springframework.security.ldap.search.LdapUserSearch; +import org.springframework.security.ldap.userdetails.LdapUserDetails; + +/** + * Abstract LDAP based implementation of a login identity provider. + */ +public class LdapProvider implements LoginIdentityProvider { + + private static final Logger logger = LoggerFactory.getLogger(LdapProvider.class); + + private AbstractLdapAuthenticationProvider provider; + private String issuer; + private long expiration; + + @Override + public final void initialize(final LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException { + this.issuer = getClass().getSimpleName(); + } + + @Override + public final void onConfigured(final LoginIdentityProviderConfigurationContext configurationContext) throws ProviderCreationException { + final String rawExpiration = configurationContext.getProperty("Authentication Expiration"); + if (StringUtils.isBlank(rawExpiration)) { + throw new ProviderCreationException("The Authentication Expiration must be specified."); + } + + try { + expiration = FormatUtils.getTimeDuration(rawExpiration, TimeUnit.MILLISECONDS); + } catch (final IllegalArgumentException iae) { + throw new ProviderCreationException(String.format("The Expiration Duration '%s' is not a valid time duration", rawExpiration)); + } + + final LdapContextSource context = new LdapContextSource(); + + final Map baseEnvironment = new HashMap<>(); + + // connect/read time out + setTimeout(configurationContext, baseEnvironment, "Connect Timeout", "com.sun.jndi.ldap.connect.timeout"); + setTimeout(configurationContext, baseEnvironment, "Read Timeout", "com.sun.jndi.ldap.read.timeout"); + + // set the base environment is necessary + if (!baseEnvironment.isEmpty()) { + context.setBaseEnvironmentProperties(baseEnvironment); + } + + // authentication strategy + final String rawAuthenticationStrategy = configurationContext.getProperty("Authentication Strategy"); + final LdapAuthenticationStrategy authenticationStrategy; + try { + authenticationStrategy = LdapAuthenticationStrategy.valueOf(rawAuthenticationStrategy); + } catch (final IllegalArgumentException iae) { + throw new ProviderCreationException(String.format("Unrecognized authentication strategy '%s'. Possible values are [%s]", + rawAuthenticationStrategy, StringUtils.join(LdapAuthenticationStrategy.values(), ", "))); + } + + switch (authenticationStrategy) { + case ANONYMOUS: + context.setAnonymousReadOnly(true); + break; + default: + final String userDn = configurationContext.getProperty("Manager DN"); + final String password = configurationContext.getProperty("Manager Password"); + + context.setUserDn(userDn); + context.setPassword(password); + + switch (authenticationStrategy) { + case SIMPLE: + context.setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy()); + break; + case START_TLS: + final AbstractTlsDirContextAuthenticationStrategy tlsAuthenticationStrategy = new DefaultTlsDirContextAuthenticationStrategy(); + + // shutdown gracefully + final String rawShutdownGracefully = configurationContext.getProperty("TLS - Shutdown Gracefully"); + if (StringUtils.isNotBlank(rawShutdownGracefully)) { + final boolean shutdownGracefully = Boolean.TRUE.toString().equalsIgnoreCase(rawShutdownGracefully); + tlsAuthenticationStrategy.setShutdownTlsGracefully(shutdownGracefully); + } + + final String rawKeystore = configurationContext.getProperty("TLS - Keystore"); + final String rawKeystorePassword = configurationContext.getProperty("TLS - Keystore Password"); + final String rawKeystoreType = configurationContext.getProperty("TLS - Keystore Type"); + final String rawTruststore = configurationContext.getProperty("TLS - Truststore"); + final String rawTruststorePassword = configurationContext.getProperty("TLS - Truststore Password"); + final String rawTruststoreType = configurationContext.getProperty("TLS - Truststore Type"); + final String rawClientAuth = configurationContext.getProperty("TLS - Client Auth"); + final String rawProtocol = configurationContext.getProperty("TLS - Protocol"); + + final ClientAuth clientAuth; + if (StringUtils.isBlank(rawClientAuth)) { + clientAuth = ClientAuth.NONE; + } else { + try { + clientAuth = ClientAuth.valueOf(rawClientAuth); + } catch (final IllegalArgumentException iae) { + throw new ProviderCreationException(String.format("Unrecognized client auth '%s'. Possible values are [%s]", + rawClientAuth, StringUtils.join(ClientAuth.values(), ", "))); + } + } + + try { + final SSLContext sslContext; + if (StringUtils.isBlank(rawKeystore)) { + sslContext = SslContextFactory.createTrustSslContext(rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, rawProtocol); + } else { + if (StringUtils.isBlank(rawTruststore)) { + sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, rawProtocol); + } else { + sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, + rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, clientAuth, rawProtocol); + } + } + tlsAuthenticationStrategy.setSslSocketFactory(sslContext.getSocketFactory()); + } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException | IOException e) { + throw new ProviderCreationException(e.getMessage(), e); + } + + context.setAuthenticationStrategy(tlsAuthenticationStrategy); + break; + } + break; + } + + // referrals + final String rawReferralStrategy = configurationContext.getProperty("Referral Strategy"); + + final ReferralStrategy referralStrategy; + try { + referralStrategy = ReferralStrategy.valueOf(rawReferralStrategy); + } catch (final IllegalArgumentException iae) { + throw new ProviderCreationException(String.format("Unrecgonized authentication strategy '%s'. Possible values are [%s]", + rawAuthenticationStrategy, StringUtils.join(ReferralStrategy.values(), ", "))); + } + + context.setReferral(referralStrategy.toString()); + + // url + final String url = configurationContext.getProperty("Url"); + + if (StringUtils.isBlank(url)) { + throw new ProviderCreationException("LDAP identity provider 'Url' must be specified."); + } + + // connection + context.setUrl(url); + + // search criteria + final String userSearchBase = configurationContext.getProperty("User Search Base"); + final String userSearchFilter = configurationContext.getProperty("User Search Filter"); + + if (StringUtils.isBlank(userSearchBase) || StringUtils.isBlank(userSearchFilter)) { + throw new ProviderCreationException("LDAP identity provider 'User Search Base' and 'User Search Filter' must be specified."); + } + + final LdapUserSearch userSearch = new FilterBasedLdapUserSearch(userSearchBase, userSearchFilter, context); + + // bind + final BindAuthenticator authenticator = new BindAuthenticator(context); + authenticator.setUserSearch(userSearch); + + try { + // handling initializing beans + context.afterPropertiesSet(); + authenticator.afterPropertiesSet(); + } catch (final Exception e) { + throw new ProviderCreationException(e.getMessage(), e); + } + + // create the underlying provider + provider = new LdapAuthenticationProvider(authenticator); + } + + private void setTimeout(final LoginIdentityProviderConfigurationContext configurationContext, + final Map baseEnvironment, + final String configurationProperty, + final String environmentKey) { + + final String rawTimeout = configurationContext.getProperty(configurationProperty); + if (StringUtils.isNotBlank(rawTimeout)) { + try { + final Long timeout = FormatUtils.getTimeDuration(rawTimeout, TimeUnit.MILLISECONDS); + baseEnvironment.put(environmentKey, timeout.toString()); + } catch (final IllegalArgumentException iae) { + throw new ProviderCreationException(String.format("The %s '%s' is not a valid time duration", configurationProperty, rawTimeout)); + } + } + } + + @Override + public final AuthenticationResponse authenticate(final LoginCredentials credentials) throws InvalidLoginCredentialsException, IdentityAccessException { + if (provider == null) { + throw new IdentityAccessException("The LDAP authentication provider is not initialized."); + } + + try { + // perform the authentication + final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword()); + final Authentication authentication = provider.authenticate(token); + + // attempt to get the ldap user details to get the DN + if (authentication.getPrincipal() instanceof LdapUserDetails) { + final LdapUserDetails userDetails = (LdapUserDetails) authentication.getPrincipal(); + return new AuthenticationResponse(userDetails.getDn(), credentials.getUsername(), expiration, issuer); + } else { + return new AuthenticationResponse(authentication.getName(), credentials.getUsername(), expiration, issuer); + } + } catch (final CommunicationException | AuthenticationServiceException e) { + logger.error(e.getMessage()); + if (logger.isDebugEnabled()) { + logger.debug(StringUtils.EMPTY, e); + } + throw new IdentityAccessException("Unable to query the configured directory server. See the logs for additional details.", e); + } catch (final BadCredentialsException bce) { + throw new InvalidLoginCredentialsException(bce.getMessage(), bce); + } + } + + @Override + public final void preDestruction() throws ProviderDestructionException { + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/registration.css b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ReferralStrategy.java similarity index 66% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/registration.css rename to nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ReferralStrategy.java index d4fdc7e048..f4c513153c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/registration.css +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/ReferralStrategy.java @@ -14,32 +14,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - Registration form styles. -*/ +package org.apache.nifi.ldap; + +/** + * + */ +public enum ReferralStrategy { + + FOLLOW("follow"), + INGORE("ignore"), + THROW("throw"); + + private final String value; + + private ReferralStrategy(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } -#registration-pane { - z-index: 1299; } - -#registration-form { - margin-top: 10px; - width: 610px; -} - -#expand-registration-button { - width: 10px; - height: 10px; - float: left; - margin-right: 5px; -} - -#expand-registration-text { - -webkit-user-select: none; - -moz-user-select: none; -} - -#registration-justification { - width: 600px; - height: 200px; -} \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider new file mode 100644 index 0000000000..b5ca1fea83 --- /dev/null +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/resources/META-INF/services/org.apache.nifi.authentication.LoginIdentityProvider @@ -0,0 +1,15 @@ +# 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. +org.apache.nifi.ldap.LdapProvider \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/pom.xml b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/pom.xml new file mode 100644 index 0000000000..e038c2cb51 --- /dev/null +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + + org.apache.nifi + nifi-nar-bundles + 0.3.1-SNAPSHOT + + nifi-ldap-iaa-providers-bundle + pom + + nifi-ldap-iaa-providers + nifi-ldap-iaa-providers-nar + + + + + org.apache.nifi + nifi-ldap-iaa-providers + 0.3.1-SNAPSHOT + + + + \ No newline at end of file diff --git a/nifi-nar-bundles/pom.xml b/nifi-nar-bundles/pom.xml index 4c0925f957..a9c7728f4d 100644 --- a/nifi-nar-bundles/pom.xml +++ b/nifi-nar-bundles/pom.xml @@ -48,6 +48,7 @@ nifi-avro-bundle nifi-couchbase-bundle nifi-azure-bundle + nifi-ldap-iaa-providers-bundle diff --git a/pom.xml b/pom.xml index 070881223e..249f01ced6 100644 --- a/pom.xml +++ b/pom.xml @@ -1,14 +1,14 @@ +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. --> 4.0.0 @@ -91,7 +91,7 @@ 9.2.11.v20150529 4.10.4 4.1.6.RELEASE - 3.2.7.RELEASE + 4.0.3.RELEASE 1.19 2.6.2 12.0.1 @@ -290,8 +290,8 @@ 2.2.1 + lgpl licensed | We also don't use the JDBC related features of quartz for + which the dependency would matter --> c3p0 c3p0 @@ -361,7 +361,7 @@ ${spring.version} + section --> commons-logging commons-logging @@ -503,6 +503,29 @@ + + org.springframework.security + spring-security-ldap + ${spring.security.version} + + + org.springframework + spring-core + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + org.springframework + spring-tx + + + org.aspectj aspectjweaver @@ -975,6 +998,12 @@ 0.4.0-SNAPSHOT nar + + org.apache.nifi + nifi-ldap-iaa-providers-nar + 0.3.1-SNAPSHOT + nar + org.apache.nifi nifi-properties @@ -1262,7 +1291,7 @@ + brace --> @@ -1352,11 +1381,11 @@ + for contributions and for the release process. While it would be nice to + run always these plugins can considerably slow the build and have proven + to create unstable builds in our multi-module project and when building using + multiple threads. The stability issues seen with Checkstyle in multi-module + builds include false-positives and false negatives. --> contrib-check