[
https://issues.apache.org/jira/browse/NIFI-1614?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=15191494#comment-15191494
]
ASF GitHub Bot commented on NIFI-1614:
--------------------------------------
Github user jvwing commented on a diff in the pull request:
https://github.com/apache/nifi/pull/267#discussion_r55886592
--- Diff:
nifi-nar-bundles/nifi-iaa-providers-bundle/nifi-file-identity-provider/src/main/java/org/apache/nifi/authentication/file/FileIdentityProvider.java
---
@@ -0,0 +1,216 @@
+/*
+ * 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.file;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+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.bind.ValidationEvent;
+import javax.xml.bind.ValidationEventHandler;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+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.authentication.file.generated.UserCredentials;
+import org.apache.nifi.authentication.file.generated.UserCredentialsList;
+import org.apache.nifi.util.FormatUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+
+/**
+ * Identity provider for simple username/password authentication backed by
a local credentials file. The credentials
+ * file contains usernames and password hashes in bcrypt format. Any
compatible bcrypt "2a" implementation may be used
+ * to populate the credentials file.
+ * <p>
+ * The XML format of the credentials file is as follows:
+ * <pre>
+ * {@code
+ * <?xml version='1.0' encoding='utf-8'?>
+ * <credentials>
+ * <user name="user1"
passwordHash="$2a$10$ztplXcwIaUNu8JXkrS.9ge4WjorJzdUrpBh2.02Y6VXvgxkLKAtvG" />
+ * <user name="user2"
passwordHash="$2a$10$24wB0UAUsRbOXz4KRZ5KlenzcEddnhIyXMyPkpTnS/29Tt12jfJJW" />
+ * <user name="user3"
passwordHash="$2a$10$dM0d7CBH3ifNZAPKV3EDNOcljMB80y97on6I8wixH4irMw18DYEi6" />
+ * </credentials>
+ * }
+ * </pre>
+ */
+public class FileIdentityProvider implements LoginIdentityProvider {
+
+ static final String PROPERTY_CREDENTIALS_FILE = "Credentials File";
+ static final String PROPERTY_EXPIRATION_PERIOD = "Authentication
Expiration";
+
+ private static final Logger logger =
LoggerFactory.getLogger(FileIdentityProvider.class);
+ private static final String CREDENTIALS_XSD = "/credentials.xsd";
+ private static final String JAXB_GENERATED_PATH =
"org.apache.nifi.authentication.file.generated";
+ private static final JAXBContext JAXB_CONTEXT =
initializeJaxbContext();
+
+ private String issuer;
+ private long expirationPeriodMilliseconds;
+ private String credentialsFilePath;
+ private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
+ private String identifier;
+
+ private static JAXBContext initializeJaxbContext() {
+ try {
+ return JAXBContext.newInstance(JAXB_GENERATED_PATH,
FileIdentityProvider.class.getClassLoader());
+ } catch (JAXBException e) {
+ throw new RuntimeException("Failed creating JAXBContext for "
+ FileIdentityProvider.class.getCanonicalName());
+ }
+ }
+
+ private static ValidationEventHandler defaultValidationEventHandler =
new ValidationEventHandler() {
+ @Override
+ public boolean handleEvent(ValidationEvent event) {
+ return false;
+ }
+ };
+
+ static UserCredentialsList loadCredentialsList(String filePath) throws
Exception {
+ return loadCredentialsList(filePath,
defaultValidationEventHandler);
+ }
+
+ static UserCredentialsList loadCredentialsList(String filePath,
ValidationEventHandler validationEventHandler) throws Exception {
+ final File userDetailsFile = new File(filePath);
+
+ if (userDetailsFile.exists()) {
+ final SchemaFactory schemaFactory =
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ final Schema schema =
schemaFactory.newSchema(UserCredentialsList.class.getResource(CREDENTIALS_XSD));
+
+ final Unmarshaller unmarshaller =
JAXB_CONTEXT.createUnmarshaller();
+ unmarshaller.setSchema(schema);
+ unmarshaller.setEventHandler(validationEventHandler);
+ final JAXBElement<UserCredentialsList> element =
unmarshaller.unmarshal(new StreamSource(userDetailsFile),
+ UserCredentialsList.class);
+ UserCredentialsList credentialsList = element.getValue();
+ return credentialsList;
+ } else {
+ final String notFoundMessage = "The credentials configuration
file was not found at: " +
+ userDetailsFile.getAbsolutePath();
+ throw new FileNotFoundException(notFoundMessage);
+ }
+ }
+
+
+ @Override
+ public final void initialize(final
LoginIdentityProviderInitializationContext initializationContext) throws
ProviderCreationException {
+ this.identifier = initializationContext.getIdentifier();
+ this.issuer = getClass().getSimpleName();
+ }
+
+ @Override
+ public final void onConfigured(final
LoginIdentityProviderConfigurationContext configurationContext) throws
ProviderCreationException {
+ final Map<String, String> configProperties =
configurationContext.getProperties();
+ for (String propertyKey : configProperties.keySet()) {
+ String propValue = configProperties.get(propertyKey);
+ logger.debug("found property '{}': '{}'", propertyKey,
propValue);
+ }
+
+ credentialsFilePath =
configProperties.get(PROPERTY_CREDENTIALS_FILE);
+ if (credentialsFilePath == null) {
+ final String message = String.format("Identity Provider '%s'
requires a credentials file path in property '%s'",
+ identifier, PROPERTY_CREDENTIALS_FILE);
+ throw new ProviderCreationException(message);
+ }
+
+ final String rawExpirationPeriod =
configProperties.get(PROPERTY_EXPIRATION_PERIOD);
+ if (rawExpirationPeriod == null || rawExpirationPeriod.isEmpty()) {
+ final String message = String.format("Identity Provider '%s'
requires a credential expiration in property '%s'",
+ identifier, PROPERTY_EXPIRATION_PERIOD);
+ throw new ProviderCreationException(message);
+ } else {
+ try {
+ expirationPeriodMilliseconds =
FormatUtils.getTimeDuration(rawExpirationPeriod, TimeUnit.MILLISECONDS);
+ } catch (IllegalArgumentException iae) {
+ final String message = String.format("Identity Provider
'%s' property '%s' value of '%s', is not a valid time period",
+ identifier, PROPERTY_EXPIRATION_PERIOD,
rawExpirationPeriod);
+ throw new ProviderCreationException(message);
+ }
+ }
+
+ logger.debug("Identity Provider '{}' configured to use file '{}'
and expiration period of '{}'={} milliseconds",
+ identifier, credentialsFilePath, rawExpirationPeriod,
expirationPeriodMilliseconds);
+ }
+
+ String getCredentialsFilePath() {
+ return credentialsFilePath;
+ }
+
+ long getExpirationPeriod() {
+ return expirationPeriodMilliseconds;
+ }
+
+ @Override
+ public final AuthenticationResponse authenticate(final
LoginCredentials credentials) throws InvalidLoginCredentialsException,
IdentityAccessException {
+ final String loginUsername = credentials.getUsername();
+ final String loginPassword = credentials.getPassword();
+ AuthenticationResponse authResponse = null;
+
+ try {
+ UserCredentialsList credentialsList =
loadCredentialsList(credentialsFilePath);
+ List<UserCredentials> userCredentials =
credentialsList.getUser();
+ for (UserCredentials userCreds : userCredentials) {
+ final String storedUsername = userCreds.getName();
+ if (storedUsername.equals(loginUsername)) {
--- End diff --
> ```
> <credentials>
> <user name="user1" passwordHash="bcrypt(password)" />
> <user name="user1" passwordHash="bcrypt(real&Strong&Password)" />
> </credentials>
> ```
> If a login request for user1, password came in, would it succeed? What
about user1, real&Strong&Password?
I believe the unique validation constraint discussed above would prevent
this sample from running. Additionally, `authenticate()` will only match one
username, and try one password hash, defined as whichever it encountered first
in the list. I do not sort the list, just accept the default ordering from
JAXB unmarshalling. Is that bad?
> Simple Username/Password Authentication
> ---------------------------------------
>
> Key: NIFI-1614
> URL: https://issues.apache.org/jira/browse/NIFI-1614
> Project: Apache NiFi
> Issue Type: Improvement
> Components: Extensions
> Reporter: James Wing
> Priority: Minor
>
> NiFi should include a simple option for username/password authentication
> backed by a local file store. NiFi's existing certificate and LDAP
> authentication schemes are very secure. However, the configuration and setup
> is complex, making them more suitable for long-lived corporate and government
> installations, but less accessible for casual or short-term use. Simple
> username/password authentication would help more users secure more NiFi
> installations beyond anonymous admin access.
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)